25
25
package com .oracle .svm .core .thread ;
26
26
27
27
import static com .oracle .svm .core .SubstrateOptions .MultiThreaded ;
28
+ import static com .oracle .svm .core .heap .RestrictHeapAccess .Access .NO_ALLOCATION ;
28
29
import static com .oracle .svm .core .thread .ThreadingSupportImpl .Options .SupportRecurringCallback ;
29
30
31
+ import java .io .Serial ;
30
32
import java .util .concurrent .TimeUnit ;
31
33
32
34
import org .graalvm .compiler .api .replacements .Fold ;
42
44
import com .oracle .svm .core .heap .RestrictHeapAccess ;
43
45
import com .oracle .svm .core .option .HostedOptionKey ;
44
46
import com .oracle .svm .core .option .SubstrateOptionsParser ;
45
- import com .oracle .svm .core .thread .Safepoint .SafepointException ;
46
47
import com .oracle .svm .core .thread .VMThreads .ActionOnTransitionToJavaSupport ;
47
48
import com .oracle .svm .core .thread .VMThreads .StatusSupport ;
48
49
import com .oracle .svm .core .threadlocal .FastThreadLocalFactory ;
@@ -69,12 +70,8 @@ public static class Options {
69
70
* adapt to a changing frequency of safepoint checks in the code that the thread executes.
70
71
*/
71
72
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 ();
78
75
79
76
/**
80
77
* Weight of the newest sample in {@link #ewmaChecksPerNano}. Older samples have a total
@@ -107,6 +104,14 @@ public void throwException(Throwable t) {
107
104
this .requestedChecks = INITIAL_CHECKS ;
108
105
}
109
106
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
+
110
115
@ Uninterruptible (reason = "Must not contain safepoint checks." )
111
116
void evaluate () {
112
117
updateStatistics ();
@@ -208,24 +213,42 @@ private boolean isCallbackDisabled() {
208
213
}
209
214
210
215
/**
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.
213
219
*/
214
220
@ 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 )
216
222
private void invokeCallback () {
217
223
try {
218
224
callback .run (CALLBACK_ACCESS );
219
- } catch (SafepointException se ) {
220
- throw se ;
225
+ } catch (SafepointException e ) {
226
+ throw e ;
221
227
} catch (Throwable t ) {
222
228
/*
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).
226
233
*/
227
234
}
228
235
}
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
+ }
229
252
}
230
253
231
254
private static final FastThreadLocalObject <RecurringCallbackTimer > activeTimer = FastThreadLocalFactory .createObject (RecurringCallbackTimer .class , "ThreadingSupportImpl.activeTimer" );
@@ -332,9 +355,9 @@ static boolean needsNativeToJavaSlowpath() {
332
355
}
333
356
334
357
/**
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.
338
361
*/
339
362
@ Uninterruptible (reason = "Must not contain safepoint checks." )
340
363
public static void pauseRecurringCallback (@ SuppressWarnings ("unused" ) String reason ) {
@@ -370,11 +393,17 @@ public static void resumeRecurringCallbackAtNextSafepoint() {
370
393
*/
371
394
public static void resumeRecurringCallback () {
372
395
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 ());
378
407
}
379
408
}
380
409
@@ -407,7 +436,18 @@ public static boolean isRecurringCallbackSupported() {
407
436
}
408
437
409
438
@ SuppressWarnings ("unchecked" )
439
+ @ Uninterruptible (reason = "Called from uninterruptible code." , mayBeInlined = true )
410
440
private static <T extends Throwable > void throwUnchecked (Throwable exception ) throws T {
411
441
throw (T ) exception ; // T is inferred as RuntimeException, but doesn't have to be
412
442
}
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
+ }
413
453
}
0 commit comments