diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java index ef08c40d253c..3798ab6fa429 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java @@ -26,13 +26,15 @@ import java.util.Arrays; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; @AutomaticallyRegisteredImageSingleton public class IsolateListenerSupport { @@ -58,22 +60,42 @@ public static IsolateListenerSupport singleton() { @Uninterruptible(reason = "Thread state not yet set up.") public void afterCreateIsolate(Isolate isolate) { - for (int i = 0; i < listeners.length; i++) { - listeners[i].afterCreateIsolate(isolate); + for (IsolateListener listener : listeners) { + try { + listener.afterCreateIsolate(isolate); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } } } @Uninterruptible(reason = "The isolate teardown is in progress.") public void onIsolateTeardown() { - for (int i = 0; i < listeners.length; i++) { - listeners[i].onIsolateTeardown(); + for (int i = listeners.length - 1; i >= 0; i--) { + try { + listeners[i].onIsolateTeardown(); + } catch (Throwable e) { + throw VMError.shouldNotReachHere(e); + } } } public interface IsolateListener { + /** + * Implementations must not throw any exceptions. Note that the thread that creates the + * isolate is still unattached when this method is called. + */ @Uninterruptible(reason = "Thread state not yet set up.") void afterCreateIsolate(Isolate isolate); + /** + * Implementations must not throw any exceptions. Note that this method is called on + * listeners in the reverse order of {@link #afterCreateIsolate}. + * + * This method is called during isolate teardown, when the VM is guaranteed to be + * single-threaded (i.e., all other threads already exited on the OS-level). This method is + * not called for applications that use {@link JavaMainWrapper}. + */ @Uninterruptible(reason = "The isolate teardown is in progress.") default void onIsolateTeardown() { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java index e683086f8fc5..c563364605fd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Isolates.java @@ -36,12 +36,9 @@ import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; -import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.c.function.CEntryPointCreateIsolateParameters; import com.oracle.svm.core.c.function.CEntryPointErrors; import com.oracle.svm.core.c.function.CEntryPointSetup; -import com.oracle.svm.core.code.CodeInfoTable; -import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.os.CommittedMemoryProvider; import com.oracle.svm.core.util.VMError; @@ -173,17 +170,4 @@ public static PointerBase getHeapBase(Isolate isolate) { } return isolate; } - - @Uninterruptible(reason = "Tear-down in progress.") - public static int tearDownCurrent() { - freeUnmanagedMemory(); - Heap.getHeap().tearDown(); - return CommittedMemoryProvider.get().tearDown(); - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static void freeUnmanagedMemory() { - CodeInfoTable.tearDown(); - NonmovableArrays.tearDown(); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index c8546bda3d51..04c783aea1e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -69,12 +69,10 @@ import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.jni.JNIJavaVMList; import com.oracle.svm.core.jni.functions.JNIFunctionTables; -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.VMThreads; import com.oracle.svm.core.thread.VMThreads.OSThreadHandle; -import com.oracle.svm.core.util.CounterSupport; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ClassUtil; @@ -256,12 +254,12 @@ private static void runShutdown0() { PlatformThreads.singleton().joinAllNonDaemons(); /* - * Run shutdown hooks (both our own hooks and application-registered hooks. Note that this - * can start new non-daemon threads. We are not responsible to wait until they have exited. + * Run shutdown hooks (both our own hooks and application-registered hooks) and teardown + * hooks. Note that this can start new non-daemon threads. We are not responsible to wait + * until they have exited. */ RuntimeSupport.getRuntimeSupport().shutdown(); - - CounterSupport.singleton().logValues(Log.log()); + RuntimeSupport.executeTearDownHooks(); } @Uninterruptible(reason = "Thread state not set up yet.") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java index 6f23b899a910..90186d35d94d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateDiagnostics.java @@ -1270,7 +1270,9 @@ public static synchronized DiagnosticThunkRegistry singleton() { if (ImageSingletons.contains(JavaMainWrapper.JavaMainSupport.class)) { thunks.add(new DumpCommandLine()); } - thunks.add(new DumpCounters()); + if (CounterSupport.isEnabled()) { + thunks.add(new DumpCounters()); + } if (RuntimeCompilation.isEnabled()) { thunks.add(new DumpCodeCacheHistory()); thunks.add(new DumpRuntimeCodeInfoMemory()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java index 4843f02ac0a1..a2e62a3d8386 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java @@ -60,6 +60,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.c.function.CEntryPointActions; import com.oracle.svm.core.c.function.CEntryPointCreateIsolateParameters; import com.oracle.svm.core.c.function.CEntryPointErrors; @@ -69,7 +70,7 @@ import com.oracle.svm.core.graal.nodes.CEntryPointEnterNode; import com.oracle.svm.core.graal.nodes.CEntryPointLeaveNode; import com.oracle.svm.core.graal.nodes.CEntryPointUtilityNode; -import com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode; +import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.PhysicalMemory; import com.oracle.svm.core.heap.ReferenceHandler; import com.oracle.svm.core.heap.ReferenceHandlerThread; @@ -78,6 +79,7 @@ import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.option.RuntimeOptionParser; +import com.oracle.svm.core.os.CommittedMemoryProvider; import com.oracle.svm.core.os.MemoryProtectionProvider; import com.oracle.svm.core.os.VirtualMemoryProvider; import com.oracle.svm.core.snippets.SnippetRuntime; @@ -87,6 +89,7 @@ import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.Safepoint; import com.oracle.svm.core.thread.ThreadListenerSupport; +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; @@ -134,7 +137,7 @@ public final class CEntryPointSnippets extends SubstrateTemplates implements Sni public static final SubstrateForeignCallDescriptor ENTER_BY_ISOLATE = SnippetRuntime.findForeignCall(CEntryPointSnippets.class, "enterByIsolate", HAS_SIDE_EFFECT, LocationIdentity.any()); - public static final SubstrateForeignCallDescriptor DETACH_THREAD = SnippetRuntime.findForeignCall(CEntryPointSnippets.class, "detachThread", + public static final SubstrateForeignCallDescriptor DETACH_CURRENT_THREAD = SnippetRuntime.findForeignCall(CEntryPointSnippets.class, "detachCurrentThread", HAS_SIDE_EFFECT, LocationIdentity.any()); public static final SubstrateForeignCallDescriptor REPORT_EXCEPTION = SnippetRuntime.findForeignCall(CEntryPointSnippets.class, "reportException", @@ -147,7 +150,7 @@ public final class CEntryPointSnippets extends SubstrateTemplates implements Sni LocationIdentity.any()); public static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = {CREATE_ISOLATE, INITIALIZE_ISOLATE, ATTACH_THREAD, ENSURE_JAVA_THREAD, ENTER_BY_ISOLATE, - DETACH_THREAD, REPORT_EXCEPTION, TEAR_DOWN_ISOLATE, IS_ATTACHED, FAIL_FATALLY, VERIFY_ISOLATE_THREAD}; + DETACH_CURRENT_THREAD, REPORT_EXCEPTION, TEAR_DOWN_ISOLATE, IS_ATTACHED, FAIL_FATALLY, VERIFY_ISOLATE_THREAD}; @NodeIntrinsic(value = ForeignCallNode.class) public static native int runtimeCall(@ConstantNodeParameter ForeignCallDescriptor descriptor, CEntryPointCreateIsolateParameters parameters); @@ -158,6 +161,9 @@ public final class CEntryPointSnippets extends SubstrateTemplates implements Sni @NodeIntrinsic(value = ForeignCallNode.class) public static native int runtimeCall(@ConstantNodeParameter ForeignCallDescriptor descriptor, Isolate isolate); + @NodeIntrinsic(value = ForeignCallNode.class) + public static native int runtimeCall(@ConstantNodeParameter ForeignCallDescriptor descriptor); + @NodeIntrinsic(value = ForeignCallNode.class) public static native int runtimeCall(@ConstantNodeParameter ForeignCallDescriptor descriptor, IsolateThread thread); @@ -484,7 +490,7 @@ private static int attachUnattachedThread(Isolate isolate, boolean startedByIsol } else { int error = VMThreads.singleton().attachThread(thread, startedByIsolate); if (error != CEntryPointErrors.NO_ERROR) { - VMThreads.singleton().freeIsolateThread(thread); + VMThreads.singleton().freeCurrentIsolateThread(); return error; } } @@ -536,16 +542,15 @@ private static int ensureJavaThread() { } @Snippet(allowMissingProbabilities = true) - public static int detachThreadSnippet() { - IsolateThread thread = CurrentIsolate.getCurrentThread(); - return runtimeCall(DETACH_THREAD, thread); + public static int detachCurrentThreadSnippet() { + return runtimeCall(DETACH_CURRENT_THREAD); } @SubstrateForeignCallTarget(stubCallingConvention = false) @Uninterruptible(reason = "Thread state going away.") - private static int detachThread(IsolateThread currentThread) { + private static int detachCurrentThread() { try { - VMThreads.singleton().detachThread(currentThread); + VMThreads.singleton().detachCurrentThread(); } catch (Throwable t) { return CEntryPointErrors.UNCAUGHT_EXCEPTION; } @@ -558,29 +563,64 @@ public static int tearDownIsolateSnippet() { } @SubstrateForeignCallTarget(stubCallingConvention = false) - @Uninterruptible(reason = "All code executed after VMThreads#tearDown must be uninterruptible") + @Uninterruptible(reason = "Tear-down in progress - may still execute interruptible Java code in the beginning.") private static int tearDownIsolate() { try { + /* Execute interruptible code. */ if (!initiateTearDownIsolateInterruptibly()) { return CEntryPointErrors.UNSPECIFIED; } - VMThreads.singleton().tearDown(); - IsolateThread finalThread = CurrentIsolate.getCurrentThread(); - int result = Isolates.tearDownCurrent(); - // release the heap memory associated with final isolate thread - VMThreads.singleton().freeIsolateThread(finalThread); - WriteCurrentVMThreadNode.writeCurrentVMThread(WordFactory.nullPointer()); - return result; + /* After threadExit(), only uninterruptible code may be executed. */ + ThreadingSupportImpl.pauseRecurringCallback("Execution of arbitrary code is prohibited during the last teardown steps."); + + /* Shut down VM thread. */ + if (VMOperationControl.useDedicatedVMOperationThread()) { + VMOperationControl.shutdownAndDetachVMOperationThread(); + } + + /* Wait until all other threads exited on the OS-level. */ + VMThreads.singleton().waitUntilDetachedThreadsExitedOnOSLevel(); + + IsolateThread currentThread = CurrentIsolate.getCurrentThread(); + VMError.guarantee(VMThreads.firstThreadUnsafe().equal(currentThread)); + VMError.guarantee(VMThreads.nextThread(VMThreads.firstThreadUnsafe()).isNull()); + + /* Now that we are truly single-threaded, notify listeners about isolate teardown. */ + IsolateListenerSupport.singleton().onIsolateTeardown(); + + /* Free the native resources of the last thread. */ + PlatformThreads.detach(currentThread); + PlatformThreads.singleton().closeOSThreadHandle(VMThreads.getOSThreadHandle(currentThread)); + + /* Free VM-internal native memory and tear down the Java heap. */ + CodeInfoTable.tearDown(); + NonmovableArrays.tearDown(); + Heap.getHeap().tearDown(); + + /* Tear down the heap address space, including the image heap. */ + int code = CommittedMemoryProvider.get().tearDown(); + if (code != CEntryPointErrors.NO_ERROR) { + return code; + } + + /* Free the last thread. */ + VMThreads.singleton().freeCurrentIsolateThread(); + return CEntryPointErrors.NO_ERROR; } catch (Throwable t) { return reportException(t); } } - @Uninterruptible(reason = "Used as a transition between uninterruptible and interruptible code", calleeMustBe = false) + @Uninterruptible(reason = "Tear-down in progress - still safe to execute interruptible Java code.", calleeMustBe = false) private static boolean initiateTearDownIsolateInterruptibly() { RuntimeSupport.executeTearDownHooks(); - return PlatformThreads.singleton().tearDown(); + if (!PlatformThreads.tearDownOtherThreads()) { + return false; + } + + VMThreads.singleton().threadExit(); + return true; } @Snippet(allowMissingProbabilities = true) @@ -739,7 +779,7 @@ public static void registerLowerings(OptionValues options, Providers providers, private final SnippetInfo enterByIsolate; private final SnippetInfo returnFromJavaToC; - private final SnippetInfo detachThread; + private final SnippetInfo detachCurrentThread; private final SnippetInfo reportException; private final SnippetInfo tearDownIsolate; @@ -755,7 +795,7 @@ private CEntryPointSnippets(OptionValues options, Providers providers, Map pools = Collections.newSetFromMap(new IdentityHashMap<>()); Set poolsWithNonDaemons = Collections.newSetFromMap(new IdentityHashMap<>()); for (Thread thread : threads) { - if (thread == null || thread == currentThread.get()) { + assert thread != null; + if (thread == currentThread.get()) { continue; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java index 150a551318e5..a0f5ae79647c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMThreads.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.thread; +import static com.oracle.svm.core.graal.nodes.WriteCurrentVMThreadNode.writeCurrentVMThread; + import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Isolate; @@ -37,7 +39,6 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; -import com.oracle.svm.core.IsolateListenerSupport; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointErrors; @@ -263,11 +264,15 @@ public IsolateThread allocateIsolateThread(int isolateThreadSize) { return isolateThread; } - /** - * Free the native memory allocated by {@link #allocateIsolateThread}. - */ - @Uninterruptible(reason = "Thread state not set up.") - public void freeIsolateThread(IsolateThread thread) { + @Uninterruptible(reason = "Thread state no longer set up.") + public void freeCurrentIsolateThread() { + freeIsolateThread(CurrentIsolate.getCurrentThread()); + writeCurrentVMThread(WordFactory.nullPointer()); + } + + /** Free the native memory allocated by {@link #allocateIsolateThread}. */ + @Uninterruptible(reason = "Thread state no longer set up.") + protected void freeIsolateThread(IsolateThread thread) { Pointer memory = unalignedIsolateThreadMemoryTL.get(thread); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(memory); } @@ -359,60 +364,64 @@ protected int attachThread(IsolateThread thread) { } /** - * Remove an {@link IsolateThread} from the thread list. This method must be the last method - * called in every thread. + * Detaches the current thread from the isolate and frees the {@link IsolateThread} data + * structure. */ - @Uninterruptible(reason = "Manipulates the threads list; broadcasts on changes.") - public void detachThread(IsolateThread thread) { - assert thread.equal(CurrentIsolate.getCurrentThread()) : "Cannot detach different thread with this method"; - - // read thread local data (can't be accessed further below as the IsolateThread is freed) - OSThreadHandle threadHandle = OSThreadHandleTL.get(thread); - OSThreadHandle nextOsThreadToCleanup = WordFactory.nullPointer(); - boolean wasStartedByCurrentIsolate = wasStartedByCurrentIsolate(thread); - if (wasStartedByCurrentIsolate) { - nextOsThreadToCleanup = threadHandle; - } - - threadExit(thread); - /* Only uninterruptible code may be executed from now on. */ - PlatformThreads.afterThreadExit(thread); + @Uninterruptible(reason = "IsolateThread will be freed.") + public void detachCurrentThread() { + threadExit(); + detachThread(CurrentIsolate.getCurrentThread(), true); + writeCurrentVMThread(WordFactory.nullPointer()); + } - /* - * Here, all code is uninterruptible because this thread either holds the THREAD_MUTEX (see - * the JavaDoc on THREAD_MUTEX) or because the IsolateThread was already freed. The - * THREAD_MUTEX must be locked with an unspecified owner because the IsolateThread is freed - * before the mutex is unlocked, so an attaching thread could reuse the memory for its - * IsolateThread. - */ - lockThreadMutexInNativeCode(true); - OSThreadHandle threadToCleanup; + /** + * Detaches a thread from the isolate and frees the {@link IsolateThread} data structure. + * + * When modifying this method, please double-check if the handling of the last isolate thread + * needs to be modified as well, see + * {@link com.oracle.svm.core.graal.snippets.CEntryPointSnippets#tearDownIsolate}. + */ + @Uninterruptible(reason = "IsolateThread will be freed. Holds the THREAD_MUTEX.") + protected void detachThread(IsolateThread thread, boolean currentThread) { + assert currentThread == (thread == CurrentIsolate.getCurrentThread()); + assert currentThread || VMOperation.isInProgressAtSafepoint(); + + OSThreadHandle threadToCleanup = WordFactory.nullPointer(); + if (currentThread) { + lockThreadMutexInNativeCode(false); + } try { - detachThreadInSafeContext(thread); - - /*- - * It is crucial that the current thread is marked for cleanup WHILE still holding the - * thread mutex. Otherwise, the following race can happen with the teardown code: - * - This thread unlocks the thread mutex and notifies waiting threads that a thread - * was detached. - * - The teardown code realizes that the last thread was detached and checks for - * remaining operating system threads to clean up. As there are no threads marked for - * cleanup, the teardown is done. - * - This thread marks itself for cleanup and crashes because the Java heap was torn - * down. - */ - threadToCleanup = detachedOsThreadToCleanup.getAndSet(nextOsThreadToCleanup); + removeFromThreadList(thread); + PlatformThreads.detach(thread); + Heap.getHeap().detachThread(thread); - releaseThread(thread); + /* + * After detaching from the heap and removing the thread from the thread list, this + * thread may only access image heap objects. It also must not execute any write + * barriers. + */ + OSThreadHandle threadHandle = getOSThreadHandle(thread); + if (wasStartedByCurrentIsolate(thread)) { + /* Some other thread will close the thread handle of this thread. */ + threadToCleanup = detachedOsThreadToCleanup.getAndSet(threadHandle); + } else { + /* Attached threads always close their own thread handle right away. */ + PlatformThreads.singleton().closeOSThreadHandle(threadHandle); + } } finally { - THREAD_MUTEX.unlockNoTransitionUnspecifiedOwner(); + if (currentThread) { + THREAD_MUTEX.unlock(); + } } - if (!wasStartedByCurrentIsolate) { - /* If a thread was attached, we need to free its thread handle. */ - PlatformThreads.singleton().closeOSThreadHandle(threadHandle); - } + /* + * After unlocking the THREAD_MUTEX, only threads that were started by the current isolate + * may still access the image heap (we guarantee that the image heap is not unmapped as long + * as such threads are alive on the OS-level). Also note that the GC won't visit the stack + * of this thread anymore. + */ cleanupExitedOsThread(threadToCleanup); + freeIsolateThread(thread); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -443,14 +452,6 @@ private static void lockThreadMutexInNativeCode0(boolean unspecifiedOwner) { } } - @Uninterruptible(reason = "Thread is detaching and holds the THREAD_MUTEX.") - private static void releaseThread(IsolateThread thread) { - THREAD_MUTEX.guaranteeIsOwner("This mutex must be locked to prevent that a GC is triggered while detaching a thread from the heap", true); - Heap.getHeap().detachThread(thread); - singleton().freeIsolateThread(thread); - // After that point, the freed thread must not access Object data in the Java heap. - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected void cleanupExitedOsThreads() { OSThreadHandle threadToCleanup = detachedOsThreadToCleanup.getAndSet(WordFactory.nullPointer()); @@ -469,14 +470,6 @@ private void cleanupExitedOsThread(OSThreadHandle threadToCleanup) { } } - @Uninterruptible(reason = "Thread is detaching and holds the THREAD_MUTEX.") - private static void detachThreadInSafeContext(IsolateThread thread) { - PlatformThreads.detachThread(thread); - removeFromThreadList(thread); - // Signal that the VMThreads list has changed. - THREAD_LIST_CONDITION.broadcast(); - } - @Uninterruptible(reason = "Thread is detaching and holds the THREAD_MUTEX.") private static void removeFromThreadList(IsolateThread thread) { IsolateThread previous = WordFactory.nullPointer(); @@ -500,43 +493,23 @@ private static void removeFromThreadList(IsolateThread thread) { current = next; } } - } - - @Uninterruptible(reason = "Only uninterruptible code may be executed after VMThreads#threadExit.") - public void tearDown() { - ThreadingSupportImpl.pauseRecurringCallback("Execution of arbitrary code is prohibited during the last teardown steps."); - - IsolateThread curThread = CurrentIsolate.getCurrentThread(); - threadExit(curThread); - /* Only uninterruptible code may be executed from now on. */ - PlatformThreads.afterThreadExit(curThread); - - if (VMOperationControl.useDedicatedVMOperationThread()) { - VMOperationControl.shutdownAndDetachVMOperationThread(); - } - /* At this point, it is guaranteed that all other threads were detached. */ - IsolateListenerSupport.singleton().onIsolateTeardown(); - waitUntilLastOsThreadExited(); - } - - /** - * Wait until the last operating-system thread exited. This implicitly guarantees (see - * {@link #detachedOsThreadToCleanup}) that all other threads exited on the operating-system - * level as well. - */ - @Uninterruptible(reason = "Only uninterruptible code may be executed after VMThreads#threadExit.") - private void waitUntilLastOsThreadExited() { - cleanupExitedOsThreads(); + THREAD_LIST_CONDITION.broadcast(); } @Uninterruptible(reason = "Called from uninterruptible code, but still safe at this point.", calleeMustBe = false) - private static void threadExit(IsolateThread thread) { - VMError.guarantee(thread.equal(CurrentIsolate.getCurrentThread()), "Cleanup must execute in detaching thread"); - Thread javaThread = PlatformThreads.currentThread.get(thread); + public void threadExit() { + Thread javaThread = PlatformThreads.currentThread.get(); if (javaThread != null) { PlatformThreads.exit(javaThread); } + /* Only uninterruptible code may be executed from now on. */ + PlatformThreads.afterThreadExit(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "Only uninterruptible code may be executed after VMThreads#threadExit.") + public void waitUntilDetachedThreadsExitedOnOSLevel() { + cleanupExitedOsThreads(); } /** @@ -569,7 +542,8 @@ public static void detachAllThreadsExceptCurrentWithoutCleanupForTearDown() { * Returns a platform-specific handle to the current thread. This handle can for example be used * for joining a thread. Depending on the platform, it can be necessary to explicitly free the * handle when it is no longer used. To avoid leaking resources, this method should therefore - * only be called by {@link #attachThread}, when {@link #OSThreadHandleTL} is not set yet. + * only be called by {@link #attachThread}. All other places should access the thread local + * {@link #OSThreadHandleTL} instead. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) protected abstract OSThreadHandle getCurrentOSThreadHandle(); @@ -702,8 +676,12 @@ protected void operate() { while (thread.isNonNull()) { IsolateThread next = nextThread(thread); if (thread.notEqual(currentThread) && !wasStartedByCurrentIsolate(thread)) { - detachThreadInSafeContext(thread); - releaseThread(thread); + /* + * The code below is similar to VMThreads.detachCurrentThread() except that it + * doesn't call VMThreads.threadExit(). We assume that this is tolerable + * considering the immediately following tear-down. + */ + VMThreads.singleton().detachThread(thread, false); } thread = next; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java index 5e262bb516c0..8220d22ebd12 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/Counter.java @@ -47,10 +47,6 @@ * A counter that can be {@linkplain #inc() incremented}. The counting code is only emitted when an * option is enabled. Counters are {@link Group grouped} for printing. * - * Currently there is no shutdown hook in Substrate VM that is invoked automatically, so - * {@link CounterSupport#logValues} needs to be called manually at the end of the application to - * print counter values. - * * Use this class in the following way: * *
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterFeature.java
index 8a524426a5e1..fc889fa946f5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterFeature.java
@@ -25,12 +25,14 @@
 package com.oracle.svm.core.util;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 
-import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
 import org.graalvm.nativeimage.ImageSingletons;
 
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
 import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.jdk.RuntimeSupport;
 import com.oracle.svm.core.util.Counter.Group;
 
 @AutomaticallyRegisteredFeature
@@ -55,7 +57,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
                 enabledGroups.add(group);
             }
         }
-        enabledGroups.sort((g1, g2) -> g1.name.compareTo(g2.name));
-        ImageSingletons.add(CounterSupport.class, new CounterSupport(enabledGroups.toArray(new Group[enabledGroups.size()])));
+
+        if (enabledGroups.size() > 0) {
+            enabledGroups.sort(Comparator.comparing(g -> g.name));
+            ImageSingletons.add(CounterSupport.class, new CounterSupport(enabledGroups.toArray(new Group[0])));
+            RuntimeSupport.getRuntimeSupport().addShutdownHook(CounterSupport::logValues);
+        }
     }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java
index d40a2fafd249..078b88ccfb29 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/CounterSupport.java
@@ -24,13 +24,14 @@
  */
 package com.oracle.svm.core.util;
 
-import jdk.graal.compiler.api.replacements.Fold;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
 
 import com.oracle.svm.core.log.Log;
 
+import jdk.graal.compiler.api.replacements.Fold;
+
 public class CounterSupport {
     private final Counter.Group[] enabledGroups;
 
@@ -39,11 +40,20 @@ public class CounterSupport {
         this.enabledGroups = enabledGroups;
     }
 
+    @Fold
+    public static boolean isEnabled() {
+        return ImageSingletons.contains(CounterSupport.class);
+    }
+
     @Fold
     public static CounterSupport singleton() {
         return ImageSingletons.lookup(CounterSupport.class);
     }
 
+    public static void logValues(@SuppressWarnings("unused") boolean firstIsolate) {
+        CounterSupport.singleton().logValues(Log.log());
+    }
+
     /**
      * Prints all counters of all enabled groups to the {@link Log}.
      */
@@ -56,5 +66,4 @@ public void logValues(Log log) {
     public boolean hasCounters() {
         return enabledGroups != null && enabledGroups.length > 0;
     }
-
 }