Skip to content

Commit 9d3d859

Browse files
Reference handler and VM operation thread fixes.
1 parent 782b28c commit 9d3d859

File tree

6 files changed

+151
-90
lines changed

6 files changed

+151
-90
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,12 @@ private static int tearDownIsolate() {
678678
/* After threadExit(), only uninterruptible code may be executed. */
679679
RecurringCallbackSupport.suspendCallbackTimer("Execution of arbitrary code is prohibited during the last teardown steps.");
680680

681-
/* Shut down VM thread. */
681+
/* Wait until the reference handler thread detaches (it was already stopped earlier). */
682+
if (ReferenceHandler.useDedicatedThread()) {
683+
ReferenceHandlerThread.waitUntilDetached();
684+
}
685+
686+
/* Shut down VM operation thread. */
682687
if (VMOperationControl.useDedicatedVMOperationThread()) {
683688
VMOperationControl.shutdownAndDetachVMOperationThread();
684689
}
@@ -724,7 +729,16 @@ private static boolean initiateTearDownIsolateInterruptibly() {
724729
return false;
725730
}
726731

732+
/*
733+
* At this point, only the current thread, the VM operation thread, and the reference
734+
* handler thread are still running.
735+
*/
736+
if (ReferenceHandler.useDedicatedThread()) {
737+
ReferenceHandlerThread.initiateShutdown();
738+
}
739+
727740
VMThreads.singleton().threadExit();
741+
/* After threadExit(), only uninterruptible code may be executed. */
728742
return true;
729743
}
730744

@@ -802,7 +816,7 @@ static boolean runtimeAssertionsEnabled() {
802816
@SubstrateForeignCallTarget(stubCallingConvention = false)
803817
private static int verifyIsolateThread(IsolateThread thread) {
804818
VMError.guarantee(CurrentIsolate.getCurrentThread() == thread, "Threads must match for the call below");
805-
if (!VMThreads.singleton().verifyIsCurrentThread(thread) || !VMThreads.singleton().verifyThreadIsAttached(thread)) {
819+
if (!VMThreads.singleton().verifyIsCurrentThread(thread) || !VMThreads.isAttached(thread)) {
806820
throw VMError.shouldNotReachHere("A call from native code to Java code provided the wrong JNI environment or the wrong IsolateThread. " +
807821
"The JNI environment / IsolateThread is a thread-local data structure and must not be shared between threads.");
808822

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,22 @@
2424
*/
2525
package com.oracle.svm.core.heap;
2626

27+
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
28+
2729
import java.lang.ref.Reference;
2830

2931
import com.oracle.svm.core.IsolateArgumentParser;
3032
import com.oracle.svm.core.NeverInline;
3133
import com.oracle.svm.core.SubstrateOptions;
3234
import com.oracle.svm.core.SubstrateUtil;
35+
import com.oracle.svm.core.Uninterruptible;
3336
import com.oracle.svm.core.stack.StackOverflowCheck;
3437
import com.oracle.svm.core.util.VMError;
3538

3639
import jdk.internal.ref.CleanerFactory;
3740

3841
public final class ReferenceHandler {
42+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
3943
public static boolean useDedicatedThread() {
4044
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
4145
return ReferenceHandlerThread.isSupported() && IsolateArgumentParser.singleton().getBooleanOptionValue(automaticReferenceHandling);

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

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@
4141
import com.oracle.svm.core.util.VMError;
4242

4343
import jdk.graal.compiler.api.replacements.Fold;
44+
import jdk.graal.compiler.word.Word;
4445

4546
public final class ReferenceHandlerThread implements Runnable {
4647
private final Thread thread;
4748
private volatile IsolateThread isolateThread;
49+
private volatile boolean stopped;
4850

4951
@Platforms(Platform.HOSTED_ONLY.class)
5052
ReferenceHandlerThread() {
@@ -54,24 +56,48 @@ public final class ReferenceHandlerThread implements Runnable {
5456
}
5557

5658
public static void start() {
57-
if (isSupported()) {
58-
singleton().thread.start();
59+
if (!isSupported()) {
60+
return;
5961
}
62+
63+
singleton().thread.start();
64+
/* Wait until the isolateThread field is initialized. */
65+
while (singleton().isolateThread.isNull()) {
66+
Thread.yield();
67+
}
68+
}
69+
70+
public static void initiateShutdown() {
71+
if (!isSupported()) {
72+
return;
73+
}
74+
75+
singleton().stopped = true;
76+
Heap.getHeap().wakeUpReferencePendingListWaiters();
77+
}
78+
79+
@Uninterruptible(reason = "Executed during teardown after VMThreads#threadExit")
80+
public static void waitUntilDetached() {
81+
if (!isSupported()) {
82+
return;
83+
}
84+
85+
VMThreads.waitInNativeUntilDetached(singleton().isolateThread);
86+
singleton().isolateThread = Word.nullPointer();
6087
}
6188

6289
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
6390
public static boolean isReferenceHandlerThread() {
64-
if (isSupported()) {
65-
return CurrentIsolate.getCurrentThread() == singleton().isolateThread;
66-
}
67-
return false;
91+
return isReferenceHandlerThread(CurrentIsolate.getCurrentThread());
92+
}
93+
94+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
95+
public static boolean isReferenceHandlerThread(IsolateThread other) {
96+
return isSupported() && other == singleton().isolateThread;
6897
}
6998

7099
public static boolean isReferenceHandlerThread(Thread other) {
71-
if (isSupported()) {
72-
return other == singleton().thread;
73-
}
74-
return false;
100+
return isSupported() && other == singleton().thread;
75101
}
76102

77103
@Override
@@ -80,19 +106,13 @@ public void run() {
80106

81107
this.isolateThread = CurrentIsolate.getCurrentThread();
82108
try {
83-
while (true) {
109+
while (!stopped) {
84110
ReferenceInternals.waitForPendingReferences();
85111
ReferenceInternals.processPendingReferences();
86112
ReferenceHandler.processCleaners();
87113
}
88-
} catch (InterruptedException e) {
89-
VMError.guarantee(VMThreads.isTearingDown(), "Reference Handler should only be interrupted during tear-down");
90114
} catch (Throwable t) {
91-
if (t instanceof OutOfMemoryError && VMThreads.isTearingDown()) {
92-
// Likely failed to allocate the InterruptedException, ignore either way.
93-
} else {
94-
throw VMError.shouldNotReachHere("Reference processing and cleaners must handle all potential exceptions", t);
95-
}
115+
throw VMError.shouldNotReachHere("Reference processing and cleaners must handle all potential exceptions", t);
96116
}
97117
}
98118

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

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import java.util.concurrent.atomic.AtomicBoolean;
4848
import java.util.concurrent.atomic.AtomicInteger;
4949

50-
import jdk.graal.compiler.word.Word;
5150
import org.graalvm.nativeimage.CurrentIsolate;
5251
import org.graalvm.nativeimage.ImageInfo;
5352
import org.graalvm.nativeimage.ImageSingletons;
@@ -89,6 +88,7 @@
8988
import com.oracle.svm.core.heap.VMOperationInfos;
9089
import com.oracle.svm.core.jdk.StackTraceUtils;
9190
import com.oracle.svm.core.jdk.UninterruptibleUtils;
91+
import com.oracle.svm.core.locks.VMCondition;
9292
import com.oracle.svm.core.locks.VMMutex;
9393
import com.oracle.svm.core.log.Log;
9494
import com.oracle.svm.core.memory.NativeMemory;
@@ -109,6 +109,7 @@
109109

110110
import jdk.graal.compiler.api.replacements.Fold;
111111
import jdk.graal.compiler.core.common.SuppressFBWarnings;
112+
import jdk.graal.compiler.word.Word;
112113
import jdk.internal.misc.Unsafe;
113114

114115
/**
@@ -544,7 +545,11 @@ public void closeOSThreadHandle(OSThreadHandle threadHandle) {
544545
FORK_JOIN_POOL_TRY_TERMINATE_METHOD = ReflectionUtil.lookupMethod(ForkJoinPool.class, "tryTerminate", boolean.class, boolean.class);
545546
}
546547

547-
/** Have each thread, except this one, tear itself down. */
548+
/**
549+
* Interrupts all threads except for the current thread and any threads that require custom
550+
* teardown logic (see {@link #isVMInternalThread(IsolateThread)}). Waits until the interrupted
551+
* threads detach.
552+
*/
548553
public static boolean tearDownOtherThreads() {
549554
final Log trace = Log.noopLog().string("[PlatformThreads.tearDownPlatformThreads:").newline().flush();
550555

@@ -557,9 +562,9 @@ public static boolean tearDownOtherThreads() {
557562
*/
558563
VMThreads.setTearingDown();
559564

560-
/* Fetch all running application threads and interrupt them. */
565+
/* Fetch threads and interrupt them. */
561566
ArrayList<Thread> threads = new ArrayList<>();
562-
FetchApplicationThreadsOperation operation = new FetchApplicationThreadsOperation(threads);
567+
FetchThreadsForTeardownOperation operation = new FetchThreadsForTeardownOperation(threads);
563568
operation.enqueue();
564569

565570
Set<ExecutorService> pools = Collections.newSetFromMap(new IdentityHashMap<>());
@@ -626,9 +631,9 @@ public static boolean tearDownOtherThreads() {
626631
return result;
627632
}
628633

629-
/** Wait (im)patiently for the VMThreads list to drain. */
634+
/** Wait (im)patiently for the thread list to drain. */
630635
private static boolean waitForTearDown() {
631-
assert isApplicationThread(CurrentIsolate.getCurrentThread()) : "we count the application threads until only the current one remains";
636+
assert !isVMInternalThread(CurrentIsolate.getCurrentThread()) : "we count the threads until only the current one remains";
632637

633638
final Log trace = Log.noopLog().string("[PlatformThreads.waitForTearDown:").newline();
634639
final long warningNanos = SubstrateOptions.getTearDownWarningNanos();
@@ -666,8 +671,8 @@ private static boolean waitForTearDown() {
666671
}
667672
}
668673

669-
private static boolean isApplicationThread(IsolateThread isolateThread) {
670-
return !VMOperationControl.isDedicatedVMOperationThread(isolateThread);
674+
public static boolean isVMInternalThread(IsolateThread thread) {
675+
return VMOperationControl.isDedicatedVMOperationThread(thread) || ReferenceHandlerThread.isReferenceHandlerThread(thread);
671676
}
672677

673678
@SuppressFBWarnings(value = "NN", justification = "notifyAll is necessary for Java semantics, no shared state needs to be modified beforehand")
@@ -762,7 +767,10 @@ static void incrementNonDaemonThreads() {
762767
assert numThreads > 0;
763768
}
764769

765-
/** A caller must call THREAD_LIST_CONDITION.broadcast() manually. */
770+
/**
771+
* Callers must manually invoke {@link VMCondition#broadcast()} on
772+
* {@link VMThreads#THREAD_LIST_CONDITION} to notify any threads waiting for changes.
773+
*/
766774
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
767775
private static void decrementNonDaemonThreads() {
768776
int numThreads = nonDaemonThreads.decrementAndGet();
@@ -1127,14 +1135,15 @@ protected void operate() {
11271135
}
11281136

11291137
/**
1130-
* Builds a list of all application threads. This must be done in a VM operation because only
1131-
* there we are allowed to allocate Java memory while holding the {@link VMThreads#THREAD_MUTEX}
1138+
* Builds a list of all threads that don't need any custom teardown logic. This must be done in
1139+
* a VM operation because only there we are allowed to allocate Java memory while holding the
1140+
* {@link VMThreads#THREAD_MUTEX}.
11321141
*/
1133-
private static class FetchApplicationThreadsOperation extends JavaVMOperation {
1142+
private static class FetchThreadsForTeardownOperation extends JavaVMOperation {
11341143
private final List<Thread> list;
11351144

1136-
FetchApplicationThreadsOperation(List<Thread> list) {
1137-
super(VMOperationInfos.get(FetchApplicationThreadsOperation.class, "Fetch application threads", SystemEffect.NONE));
1145+
FetchThreadsForTeardownOperation(List<Thread> list) {
1146+
super(VMOperationInfos.get(FetchThreadsForTeardownOperation.class, "Fetch threads for teardown", SystemEffect.NONE));
11381147
this.list = list;
11391148
}
11401149

@@ -1144,11 +1153,13 @@ public void operate() {
11441153
VMMutex lock = VMThreads.THREAD_MUTEX.lock();
11451154
try {
11461155
for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) {
1147-
if (isApplicationThread(isolateThread)) {
1148-
final Thread thread = PlatformThreads.fromVMThread(isolateThread);
1149-
if (thread != null) {
1150-
list.add(thread);
1151-
}
1156+
if (isVMInternalThread(isolateThread)) {
1157+
continue;
1158+
}
1159+
1160+
Thread thread = PlatformThreads.fromVMThread(isolateThread);
1161+
if (thread != null) {
1162+
list.add(thread);
11521163
}
11531164
}
11541165
} finally {
@@ -1185,24 +1196,26 @@ public void operate() {
11851196
VMMutex lock = VMThreads.THREAD_MUTEX.lock();
11861197
try {
11871198
for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) {
1188-
if (isApplicationThread(isolateThread)) {
1189-
attachedCount++;
1190-
if (printLaggards.get() && trace.isEnabled() && isolateThread != queuingThread) {
1191-
trace.string(" laggard isolateThread: ").hex(isolateThread);
1192-
final Thread thread = PlatformThreads.fromVMThread(isolateThread);
1193-
if (thread != null) {
1194-
final String name = thread.getName();
1195-
final Thread.State status = thread.getState();
1196-
final boolean interruptedStatus = JavaThreads.isInterrupted(thread);
1197-
trace.string(" thread.getName(): ").string(name)
1198-
.string(" interruptedStatus: ").bool(interruptedStatus)
1199-
.string(" getState(): ").string(status.name()).newline();
1200-
for (StackTraceElement e : thread.getStackTrace()) {
1201-
trace.string(e.toString()).newline();
1202-
}
1199+
if (isVMInternalThread(isolateThread)) {
1200+
continue;
1201+
}
1202+
1203+
attachedCount++;
1204+
if (printLaggards.get() && trace.isEnabled() && isolateThread != queuingThread) {
1205+
trace.string(" laggard isolateThread: ").hex(isolateThread);
1206+
final Thread thread = PlatformThreads.fromVMThread(isolateThread);
1207+
if (thread != null) {
1208+
final String name = thread.getName();
1209+
final Thread.State status = thread.getState();
1210+
final boolean interruptedStatus = JavaThreads.isInterrupted(thread);
1211+
trace.string(" thread.getName(): ").string(name)
1212+
.string(" interruptedStatus: ").bool(interruptedStatus)
1213+
.string(" getState(): ").string(status.name()).newline();
1214+
for (StackTraceElement e : thread.getStackTrace()) {
1215+
trace.string(e.toString()).newline();
12031216
}
1204-
trace.newline().flush();
12051217
}
1218+
trace.newline().flush();
12061219
}
12071220
}
12081221

0 commit comments

Comments
 (0)