Skip to content

Commit 7332df5

Browse files
authored
Merge pull request #139 from synecdoche/GR-63795
[Backport] [Oracle GraalVM] [GR-63795] Backport to 23.1: Verify that recurring callbacks are allocation free.
2 parents a5e17aa + 2167561 commit 7332df5

File tree

4 files changed

+95
-84
lines changed

4 files changed

+95
-84
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.jfr;
2626

27-
import java.lang.reflect.Field;
2827
import java.util.List;
2928

3029
import org.graalvm.compiler.api.replacements.Fold;
@@ -187,16 +186,6 @@ public static JfrLogging getJfrLogging() {
187186
return get().jfrLogging;
188187
}
189188

190-
public static Object getHandler(Class<? extends jdk.internal.event.Event> eventClass) {
191-
try {
192-
Field f = eventClass.getDeclaredField("eventHandler");
193-
f.setAccessible(true);
194-
return f.get(null);
195-
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
196-
throw new InternalError("Could not access event handler");
197-
}
198-
}
199-
200189
@Uninterruptible(reason = "Prevent races with VM operations that start/stop recording.", callerMustBe = true)
201190
protected boolean isRecording() {
202191
return recording;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,25 @@ private static long getSafepointPromptnessFailureNanos() {
180180
return Options.SafepointPromptnessFailureNanos.getValue().longValue();
181181
}
182182

183-
/**
184-
* Used to wrap exceptions that are explicitly thrown by recurring callbacks.
185-
*/
186-
static class SafepointException extends RuntimeException {
187-
private static final long serialVersionUID = 1L;
188-
189-
final Throwable inner;
190-
191-
SafepointException(Throwable inner) {
192-
this.inner = inner;
183+
@Uninterruptible(reason = "Must not contain safepoint checks.")
184+
private static void slowPathSafepointCheck(int newStatus, boolean callerHasJavaFrameAnchor, boolean popFrameAnchor) throws Throwable {
185+
try {
186+
slowPathSafepointCheck0(newStatus, callerHasJavaFrameAnchor, popFrameAnchor);
187+
} catch (ThreadingSupportImpl.SafepointException e) {
188+
/* This exception is intended to be thrown from safepoint checks, at one's own risk */
189+
throw ThreadingSupportImpl.RecurringCallbackTimer.getAndClearPendingException();
190+
} catch (Throwable ex) {
191+
/*
192+
* The foreign call from snippets to this method does not have an exception edge. So we
193+
* could miss an exception handler if we unwind an exception from this method.
194+
*
195+
* Any exception coming out of a safepoint would be surprising to users. There is a good
196+
* reason why Thread.stop() has been deprecated a long time ago (we do not support it on
197+
* Substrate VM).
198+
*/
199+
VMError.shouldNotReachHere(ex);
193200
}
201+
exitSlowPathCheck();
194202
}
195203

196204
/**
@@ -202,7 +210,7 @@ static class SafepointException extends RuntimeException {
202210
* </ul>
203211
**/
204212
@Uninterruptible(reason = "Must not contain safepoint checks.")
205-
private static void slowPathSafepointCheck(int newStatus, boolean callerHasJavaFrameAnchor, boolean popFrameAnchor) {
213+
private static void slowPathSafepointCheck0(int newStatus, boolean callerHasJavaFrameAnchor, boolean popFrameAnchor) {
206214
final IsolateThread myself = CurrentIsolate.getCurrentThread();
207215

208216
SafepointListenerSupport.singleton().beforeSlowpathSafepointCheck();
@@ -412,32 +420,9 @@ public static void slowPathSafepointCheck() throws Throwable {
412420
Safepoint.setSafepointRequested(THREAD_REQUEST_RESET);
413421
return;
414422
}
415-
VMError.guarantee(StatusSupport.isStatusJava(), "Attempting to do a safepoint check when not in Java mode");
416423

417-
try {
418-
/*
419-
* Block on mutex held by thread that requested safepoint, i.e., transition to native
420-
* code.
421-
*/
422-
slowPathSafepointCheck(StatusSupport.STATUS_IN_JAVA, false, false);
423-
424-
} catch (SafepointException se) {
425-
/* This exception is intended to be thrown from safepoint checks, at one's own risk */
426-
throw se.inner;
427-
428-
} catch (Throwable ex) {
429-
/*
430-
* The foreign call from snippets to this method does not have an exception edge. So we
431-
* could miss an exception handler if we unwind an exception from this method.
432-
*
433-
* Any exception coming out of a safepoint would be surprising to users. There is a good
434-
* reason why Thread.stop() has been deprecated a long time ago (we do not support it on
435-
* Substrate VM).
436-
*/
437-
VMError.shouldNotReachHere(ex);
438-
}
439-
440-
exitSlowPathCheck();
424+
VMError.guarantee(StatusSupport.isStatusJava(), "Attempting to do a safepoint check when not in Java mode");
425+
slowPathSafepointCheck(StatusSupport.STATUS_IN_JAVA, false, false);
441426
}
442427

443428
@Uninterruptible(reason = "Must not contain safepoint checks")
@@ -544,7 +529,7 @@ public static void transitionVMToNative() {
544529
*/
545530
@SubstrateForeignCallTarget(stubCallingConvention = false)
546531
@Uninterruptible(reason = "Must not contain safepoint checks")
547-
private static void enterSlowPathTransitionFromNativeToNewStatus(int newStatus, boolean popFrameAnchor) {
532+
private static void enterSlowPathTransitionFromNativeToNewStatus(int newStatus, boolean popFrameAnchor) throws Throwable {
548533
VMError.guarantee(StatusSupport.isStatusNativeOrSafepoint(), "Must either be at a safepoint or in native mode");
549534
VMError.guarantee(!SafepointBehavior.ignoresSafepoints(),
550535
"The safepoint handling doesn't change the status of threads that ignore safepoints. So, the fast path transition must succeed and this slow path must not be called");

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ThreadingSupportImpl.java

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
package com.oracle.svm.core.thread;
2626

2727
import static com.oracle.svm.core.SubstrateOptions.MultiThreaded;
28+
import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION;
2829
import static com.oracle.svm.core.thread.ThreadingSupportImpl.Options.SupportRecurringCallback;
2930

31+
import java.io.Serial;
3032
import java.util.concurrent.TimeUnit;
3133

3234
import org.graalvm.compiler.api.replacements.Fold;
@@ -42,7 +44,6 @@
4244
import com.oracle.svm.core.heap.RestrictHeapAccess;
4345
import com.oracle.svm.core.option.HostedOptionKey;
4446
import com.oracle.svm.core.option.SubstrateOptionsParser;
45-
import com.oracle.svm.core.thread.Safepoint.SafepointException;
4647
import com.oracle.svm.core.thread.VMThreads.ActionOnTransitionToJavaSupport;
4748
import com.oracle.svm.core.thread.VMThreads.StatusSupport;
4849
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
@@ -69,12 +70,8 @@ public static class Options {
6970
* adapt to a changing frequency of safepoint checks in the code that the thread executes.
7071
*/
7172
public static class RecurringCallbackTimer {
72-
private static final RecurringCallbackAccess CALLBACK_ACCESS = new RecurringCallbackAccess() {
73-
@Override
74-
public void throwException(Throwable t) {
75-
throw new SafepointException(t);
76-
}
77-
};
73+
private static final FastThreadLocalObject<Throwable> EXCEPTION_TL = FastThreadLocalFactory.createObject(Throwable.class, "RecurringCallbackTimer.exception");
74+
private static final RecurringCallbackAccess CALLBACK_ACCESS = new RecurringCallbackAccessImpl();
7875

7976
/**
8077
* Weight of the newest sample in {@link #ewmaChecksPerNano}. Older samples have a total
@@ -107,6 +104,14 @@ public void throwException(Throwable t) {
107104
this.requestedChecks = INITIAL_CHECKS;
108105
}
109106

107+
@Uninterruptible(reason = "Prevent recurring callback execution.", callerMustBe = true)
108+
public static Throwable getAndClearPendingException() {
109+
Throwable t = EXCEPTION_TL.get();
110+
VMError.guarantee(t != null, "There must be a recurring callback exception pending.");
111+
EXCEPTION_TL.set(null);
112+
return t;
113+
}
114+
110115
@Uninterruptible(reason = "Must not contain safepoint checks.")
111116
void evaluate() {
112117
updateStatistics();
@@ -208,24 +213,42 @@ private boolean isCallbackDisabled() {
208213
}
209214

210215
/**
211-
* Separate method to invoke {@link #callback} so that {@link #evaluate()} can be strictly
212-
* {@link Uninterruptible} and allocation-free.
216+
* Recurring callbacks may be executed in any method that contains a safepoint check. This
217+
* includes methods that need to be allocation free. Therefore, recurring callbacks must not
218+
* allocate any Java heap memory.
213219
*/
214220
@Uninterruptible(reason = "Required by caller, but does not apply to callee.", calleeMustBe = false)
215-
@RestrictHeapAccess(reason = "Callee may allocate", access = RestrictHeapAccess.Access.UNRESTRICTED)
221+
@RestrictHeapAccess(reason = "Recurring callbacks must not allocate.", access = NO_ALLOCATION)
216222
private void invokeCallback() {
217223
try {
218224
callback.run(CALLBACK_ACCESS);
219-
} catch (SafepointException se) {
220-
throw se;
225+
} catch (SafepointException e) {
226+
throw e;
221227
} catch (Throwable t) {
222228
/*
223-
* Recurring callbacks are specified to ignore all exceptions. We cannot even log
224-
* the exception because that could lead to a StackOverflowError (especially when
225-
* the recurring callback failed with a StackOverflowError).
229+
* Recurring callbacks are specified to ignore exceptions (except if the exception
230+
* is thrown via RecurringCallbackAccess.throwException(), which is handled above).
231+
* We cannot even log the exception because that could lead to a StackOverflowError
232+
* (especially when the recurring callback failed with a StackOverflowError).
226233
*/
227234
}
228235
}
236+
237+
/**
238+
* We need to distinguish between arbitrary exceptions (must be swallowed) and exceptions
239+
* that are thrown via {@link RecurringCallbackAccess#throwException} (must be forwarded to
240+
* the application). When a recurring callback uses
241+
* {@link RecurringCallbackAccess#throwException}, we store the exception in a thread local
242+
* (to avoid allocations) and throw a pre-allocated marker exception instead. We catch the
243+
* marker exception internally, accesses the thread local, and rethrow that exception.
244+
*/
245+
private static final class RecurringCallbackAccessImpl implements RecurringCallbackAccess {
246+
@Override
247+
public void throwException(Throwable t) {
248+
EXCEPTION_TL.set(t);
249+
throw SafepointException.SINGLETON;
250+
}
251+
}
229252
}
230253

231254
private static final FastThreadLocalObject<RecurringCallbackTimer> activeTimer = FastThreadLocalFactory.createObject(RecurringCallbackTimer.class, "ThreadingSupportImpl.activeTimer");
@@ -332,9 +355,9 @@ static boolean needsNativeToJavaSlowpath() {
332355
}
333356

334357
/**
335-
* Recurring callbacks execute arbitrary code and can throw {@link SafepointException}s. In some
336-
* code parts (e.g., when executing VM operations), we can't deal with arbitrary code execution
337-
* and therefore need to pause the execution of recurring callbacks.
358+
* Recurring callbacks execute arbitrary code and may throw exceptions. In some code parts
359+
* (e.g., when executing VM operations), we can't deal with arbitrary code execution and
360+
* therefore need to pause the execution of recurring callbacks.
338361
*/
339362
@Uninterruptible(reason = "Must not contain safepoint checks.")
340363
public static void pauseRecurringCallback(@SuppressWarnings("unused") String reason) {
@@ -370,11 +393,17 @@ public static void resumeRecurringCallbackAtNextSafepoint() {
370393
*/
371394
public static void resumeRecurringCallback() {
372395
if (resumeCallbackExecution()) {
373-
try {
374-
onSafepointCheckSlowpath();
375-
} catch (SafepointException e) {
376-
throwUnchecked(e.inner); // needed: callers cannot declare `throws Throwable`
377-
}
396+
maybeExecuteRecurringCallback();
397+
}
398+
}
399+
400+
@Uninterruptible(reason = "Prevent safepoints.")
401+
private static void maybeExecuteRecurringCallback() {
402+
try {
403+
onSafepointCheckSlowpath();
404+
} catch (SafepointException e) {
405+
/* Callers cannot declare `throws Throwable`. */
406+
throwUnchecked(RecurringCallbackTimer.getAndClearPendingException());
378407
}
379408
}
380409

@@ -407,7 +436,18 @@ public static boolean isRecurringCallbackSupported() {
407436
}
408437

409438
@SuppressWarnings("unchecked")
439+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
410440
private static <T extends Throwable> void throwUnchecked(Throwable exception) throws T {
411441
throw (T) exception; // T is inferred as RuntimeException, but doesn't have to be
412442
}
443+
444+
static final class SafepointException extends RuntimeException {
445+
public static final SafepointException SINGLETON = new SafepointException();
446+
447+
@Serial //
448+
private static final long serialVersionUID = 1L;
449+
450+
private SafepointException() {
451+
}
452+
}
413453
}

substratevm/src/org.graalvm.polyglot.nativeapi/src/org/graalvm/polyglot/nativeapi/PolyglotNativeAPI.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,20 +2413,17 @@ private static PolyglotStatus doRegisterRecurringCallback0(long intervalNanos, P
24132413
PolyglotCallbackInfoInternal info = new PolyglotCallbackInfoInternal(new ObjectHandle[0], data);
24142414
var infoHandle = (PolyglotCallbackInfo) objectHandles.create(info);
24152415
var handles = getHandles();
2416-
RecurringCallback recurringCallback = new RecurringCallback() {
2417-
@Override
2418-
public void run(Threading.RecurringCallbackAccess access) {
2419-
handles.freezeHandleCreation();
2420-
try {
2421-
callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), infoHandle);
2422-
CallbackException ce = threadLocals.get().callbackException;
2423-
if (ce != null) {
2424-
access.throwException(ce);
2425-
}
2426-
} finally {
2427-
threadLocals.get().callbackException = null;
2428-
handles.unfreezeHandleCreation();
2416+
RecurringCallback recurringCallback = access -> {
2417+
handles.freezeHandleCreation();
2418+
try {
2419+
callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), infoHandle);
2420+
CallbackException ce = threadLocals.get().callbackException;
2421+
if (ce != null) {
2422+
access.throwException(ce);
24292423
}
2424+
} finally {
2425+
threadLocals.get().callbackException = null;
2426+
handles.unfreezeHandleCreation();
24302427
}
24312428
};
24322429
try {

0 commit comments

Comments
 (0)