diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index ae554331d88c..337d103453ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -138,9 +138,6 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o } }; - @Option(help = "Support continuations (without requiring a Project Loom JDK)") // - public static final HostedOptionKey SupportContinuations = new HostedOptionKey<>(false); - public static final int ForceFallback = 10; public static final int Automatic = 5; public static final int NoFallback = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java index f1d6b364ab42..a86d95a6755f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java @@ -28,9 +28,7 @@ import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.AnnotateOriginal; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -52,10 +50,6 @@ public static String getSavedProperty(String name) { return SystemPropertiesSupport.singleton().getSavedProperties().get(name); } - @AnnotateOriginal - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static native Thread.State toThreadState(int threadStatus); - @Substitute @NeverInline("Starting a stack walk in the caller frame") public static ClassLoader latestUserDefinedLoader0() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Continuation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Continuation.java index 420d30c084e3..885868fc8899 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Continuation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Continuation.java @@ -32,7 +32,6 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.heap.StoredContinuation; import com.oracle.svm.core.heap.StoredContinuationAccess; import com.oracle.svm.core.heap.VMOperationInfos; @@ -42,15 +41,12 @@ import com.oracle.svm.core.stack.StackOverflowCheck; import com.oracle.svm.core.util.VMError; -/** - * Foundation for continuation support via {@link SubstrateVirtualThread} or - * {@linkplain Target_jdk_internal_vm_Continuation Project Loom}. - */ +/** Foundation for {@linkplain Target_jdk_internal_vm_Continuation Project Loom} support. */ @InternalVMMethod public final class Continuation { @Fold public static boolean isSupported() { - return SubstrateOptions.SupportContinuations.getValue() || LoomSupport.isEnabled(); + return LoomSupport.isEnabled(); } public static final int YIELDING = -2; @@ -69,7 +65,6 @@ public static boolean isSupported() { /** While executing, frame pointer of initial frame of continuation, {@code null} otherwise. */ private Pointer baseSP; - private boolean done; private int overflowCheckState; Continuation(Runnable target) { @@ -167,7 +162,6 @@ private void enter2() { Pointer returnSP = sp; CodePointer returnIP = ip; - done = true; ip = WordFactory.nullPointer(); sp = WordFactory.nullPointer(); baseSP = WordFactory.nullPointer(); @@ -215,18 +209,14 @@ private Integer yield0() { throw VMError.shouldNotReachHereAtRuntime(); } - public boolean isStarted() { + boolean isStarted() { return stored != null || ip.isNonNull(); } - public boolean isEmpty() { + boolean isEmpty() { return stored == null; } - public boolean isDone() { - return done; - } - private static final class TryPreemptOperation extends JavaVMOperation { int preemptStatus = FREEZE_OK; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSubstitutions.java deleted file mode 100644 index feadb52b3f17..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationSubstitutions.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.thread; - -import java.util.concurrent.locks.LockSupport; - -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.Inject; -import com.oracle.svm.core.annotate.InjectAccessors; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.ContinuationsSupported; -import com.oracle.svm.core.jdk.NotLoomJDK; - -@TargetClass(value = LockSupport.class, onlyWith = {ContinuationsSupported.class, NotLoomJDK.class}) -final class Target_java_util_concurrent_locks_LockSupport { - - @Alias static Target_jdk_internal_misc_Unsafe_JavaThreads U; - - @Alias - static native void setBlocker(Thread thread, Object blocker); - - @Substitute - static void unpark(Thread thread) { - if (thread != null) { - if (VirtualThreads.singleton().isVirtual(thread)) { - VirtualThreads.singleton().unpark(thread); - } else { - U.unpark(thread); - } - } - } - - @Substitute - static void park(Object blocker) { - Thread t = Thread.currentThread(); - setBlocker(t, blocker); - try { - if (VirtualThreads.singleton().isVirtual(t)) { - VirtualThreads.singleton().park(); - } else { - U.park(false, 0L); - } - } finally { - setBlocker(t, null); - } - } - - @Substitute - static void parkNanos(Object blocker, long nanos) { - if (nanos > 0) { - Thread t = Thread.currentThread(); - setBlocker(t, blocker); - try { - if (VirtualThreads.singleton().isVirtual(t)) { - VirtualThreads.singleton().parkNanos(nanos); - } else { - U.park(false, nanos); - } - } finally { - setBlocker(t, null); - } - } - } - - @Substitute - static void parkUntil(Object blocker, long deadline) { - Thread t = Thread.currentThread(); - setBlocker(t, blocker); - try { - if (VirtualThreads.singleton().isVirtual(t)) { - VirtualThreads.singleton().parkUntil(deadline); - } else { - U.park(true, deadline); - } - } finally { - setBlocker(t, null); - } - } - - @Substitute - static void park() { - if (JavaThreads.isCurrentThreadVirtual()) { - VirtualThreads.singleton().park(); - } else { - U.park(false, 0L); - } - } - - @Substitute - public static void parkNanos(long nanos) { - if (nanos > 0) { - if (JavaThreads.isCurrentThreadVirtual()) { - VirtualThreads.singleton().parkNanos(nanos); - ((SubstrateVirtualThread) Thread.currentThread()).parkNanos(nanos); - } else { - U.park(false, nanos); - } - } - } - - @Substitute - public static void parkUntil(long deadline) { - if (JavaThreads.isCurrentThreadVirtual()) { - VirtualThreads.singleton().parkUntil(deadline); - } else { - U.park(true, deadline); - } - } -} - -@TargetClass(className = "sun.nio.ch.NativeThreadSet", onlyWith = {ContinuationsSupported.class, NotLoomJDK.class}) -final class Target_sun_nio_ch_NativeThreadSet { - @Alias @InjectAccessors(NativeThreadSetUsedAccessors.class) // - int used; - - @Inject // - int injectedUsed; -} - -final class NativeThreadSetUsedAccessors { - static int get(Target_sun_nio_ch_NativeThreadSet that) { - return that.injectedUsed; - } - - static void set(Target_sun_nio_ch_NativeThreadSet that, int value) { - // Note that the accessing method holds a lock that prevents concurrent updates - if (JavaThreads.isCurrentThreadVirtual()) { - int diff = value - that.injectedUsed; - if (diff == 1) { - VirtualThreads.singleton().pinCurrent(); - } else if (diff == -1) { - VirtualThreads.singleton().unpinCurrent(); - } else { - assert value == 0 : "must only be incremented or decremented by 1 (or initialized to 0)"; - } - } - that.injectedUsed = value; - } - - private NativeThreadSetUsedAccessors() { - } -} - -final class ContinuationSubstitutions { - private ContinuationSubstitutions() { - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationsFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationsFeature.java index eb81923f372a..2d3fd52a9beb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationsFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/ContinuationsFeature.java @@ -36,8 +36,6 @@ import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.heap.StoredContinuation; import com.oracle.svm.core.heap.StoredContinuationAccess; -import com.oracle.svm.core.option.SubstrateOptionsParser; -import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -78,22 +76,6 @@ public void afterRegistration(AfterRegistrationAccess access) { LoomVirtualThreads vt = new LoomVirtualThreads(); ImageSingletons.add(VirtualThreads.class, vt); ImageSingletons.add(LoomVirtualThreads.class, vt); // for simpler check in LoomSupport - } else if (SubstrateOptions.SupportContinuations.getValue()) { - if (DeoptimizationSupport.enabled()) { - throw UserError.abort("Option %s is in use, but is not supported together with Truffle JIT compilation.", - SubstrateOptionsParser.commandArgument(SubstrateOptions.SupportContinuations, "+")); - } else if (SubstrateOptions.useLLVMBackend()) { - throw UserError.abort("Option %s is in use, but is not supported together with the LLVM backend.", - SubstrateOptionsParser.commandArgument(SubstrateOptions.SupportContinuations, "+")); - } else if (JavaVersionUtil.JAVA_SPEC == 17) { - ImageSingletons.add(VirtualThreads.class, new SubstrateVirtualThreads()); - } else if (JavaVersionUtil.JAVA_SPEC >= firstLoomPreviewVersion && JavaVersionUtil.JAVA_SPEC <= lastLoomPreviewVersion) { - throw UserError.abort("Virtual threads on JDK %d are supported only with preview features enabled (--enable-preview). Using option %s is unnecessary.", - JavaVersionUtil.JAVA_SPEC, SubstrateOptionsParser.commandArgument(SubstrateOptions.SupportContinuations, "+")); - } else { - throw UserError.abort("Option %s is in use, but is not supported on JDK %d.", - SubstrateOptionsParser.commandArgument(SubstrateOptions.SupportContinuations, "+"), JavaVersionUtil.JAVA_SPEC); - } } finishedRegistration = true; } @@ -129,6 +111,6 @@ public void beforeCompilation(BeforeCompilationAccess access) { } static void abortIfUnsupported() { - VMError.guarantee(Continuation.isSupported(), "Virtual thread internals are reachable but support is not available or active."); + VMError.guarantee(Continuation.isSupported(), "Virtual threads internals are reachable but support is not available or active."); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 875343b8de07..e67b2ad4613d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -27,7 +27,6 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.security.AccessControlContext; import java.security.AccessController; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -47,7 +46,6 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.jdk.JDK19OrLater; -import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.stack.StackFrameVisitor; import com.oracle.svm.util.ReflectionUtil; @@ -122,10 +120,6 @@ static boolean getAndClearInterrupt(Thread thread) { if (supportsVirtual() && isVirtual(thread)) { return VirtualThreads.singleton().getAndClearInterrupt(thread); } - return getAndClearInterruptedFlag(thread); - } - - static boolean getAndClearInterruptedFlag(Thread thread) { /* * As we don't use a lock, it is possible to observe any kinds of races with other threads * that try to set the interrupted status to true. However, those races don't cause any @@ -133,15 +127,11 @@ static boolean getAndClearInterruptedFlag(Thread thread) { * There also can't be any problematic races with other calls to check the interrupt status * because it is cleared only by the current thread. */ - boolean oldValue = isInterrupted(thread); - if (oldValue) { - writeInterruptedFlag(thread, false); + if (!isInterrupted(thread)) { + return false; } - return oldValue; - } - - static void writeInterruptedFlag(Thread thread, boolean value) { - toTarget(thread).interrupted = value; + toTarget(thread).interrupted = false; + return true; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -168,15 +158,6 @@ public static boolean isCurrentThreadVirtual() { return thread != null && toTarget(thread).vthread != null; } - @AlwaysInline("Enable constant folding in case of Loom.") - private static boolean isVirtualDisallowLoom(Thread thread) { - if (LoomSupport.isEnabled()) { - assert !isVirtual(thread) : "should not see Loom virtual thread objects here"; - return false; - } - return isVirtual(thread); - } - @SuppressFBWarnings(value = "BC", justification = "Cast for @TargetClass") static Target_java_lang_ThreadGroup toTarget(ThreadGroup threadGroup) { return Target_java_lang_ThreadGroup.class.cast(threadGroup); @@ -193,14 +174,6 @@ static void join(Thread thread, long millis) throws InterruptedException { } } - static void yieldCurrent() { - if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread()) && !LoomSupport.isEnabled()) { - VirtualThreads.singleton().yield(); - } else { - PlatformThreads.singleton().yieldCurrent(); - } - } - @NeverInline("Starting a stack walk in the caller frame") public static StackTraceElement[] getStackTrace(boolean filterExceptions, Thread thread) { /* @@ -354,43 +327,6 @@ static void initNewThreadLocalsAndLoader(Target_java_lang_Thread tjlt, boolean a } } - static void sleep(long millis) throws InterruptedException { - /* Starting with JDK 19, the thread sleep event is implemented as a Java-level event. */ - if (JavaVersionUtil.JAVA_SPEC >= 19) { - if (com.oracle.svm.core.jfr.HasJfrSupport.get() && Target_jdk_internal_event_ThreadSleepEvent.isTurnedOn()) { - Target_jdk_internal_event_ThreadSleepEvent event = new Target_jdk_internal_event_ThreadSleepEvent(); - try { - event.time = TimeUnit.MILLISECONDS.toNanos(millis); - event.begin(); - sleep0(millis); - } finally { - event.commit(); - } - } else { - sleep0(millis); - } - } else { - long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); - sleep0(millis); - ThreadSleepEventJDK17.emit(millis, startTicks); - } - } - - private static void sleep0(long millis) throws InterruptedException { - if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread()) && !LoomSupport.isEnabled()) { - VirtualThreads.singleton().sleepMillis(millis); - } else { - PlatformThreads.sleep(millis); - } - } - - static boolean isAlive(Thread thread) { - if (supportsVirtual() && isVirtualDisallowLoom(thread) && !LoomSupport.isEnabled()) { - return VirtualThreads.singleton().isAlive(thread); - } - return PlatformThreads.isAlive(thread); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void setCurrentThreadLockHelper(Object helper) { if (supportsVirtual()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java index 89c1e010e0c6..a5cfa666de02 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/LoomVirtualThreads.java @@ -32,7 +32,6 @@ import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -42,7 +41,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jdk.StackTraceUtils; import com.oracle.svm.core.stack.StackFrameVisitor; -import com.oracle.svm.core.util.VMError; /** * In a JDK that supports Project Loom virtual threads (JEP 425, starting with JDK 19 as preview @@ -77,51 +75,11 @@ public void join(Thread thread, long millis) throws InterruptedException { } } - @Platforms({}) // fails image build if reachable - private static RuntimeException unreachable() { - return VMError.shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport - } - @Override public boolean getAndClearInterrupt(Thread thread) { return cast(thread).getAndClearInterrupt(); } - @Override - public void yield() { - throw unreachable(); - } - - @Override - public void sleepMillis(long millis) { - throw unreachable(); - } - - @Override - public boolean isAlive(Thread thread) { - throw unreachable(); - } - - @Override - public void unpark(Thread thread) { - throw unreachable(); - } - - @Override - public void park() { - throw unreachable(); - } - - @Override - public void parkNanos(long nanos) { - throw unreachable(); - } - - @Override - public void parkUntil(long deadline) { - throw unreachable(); - } - @Override public void pinCurrent() { Target_jdk_internal_vm_Continuation.pin(); @@ -269,5 +227,4 @@ private static StackTraceElement[] getPlatformThreadStackTraceAtSafepoint(Thread } return StackTraceUtils.getThreadStackTraceAtSafepoint(isolateThread, WordFactory.nullPointer()); } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java index e928fa9eaed5..49c4b9d206c0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/PlatformThreads.java @@ -86,8 +86,8 @@ import com.oracle.svm.core.heap.ReferenceHandlerThread; import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.jdk.StackTraceUtils; -import com.oracle.svm.core.jdk.Target_jdk_internal_misc_VM; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17; import com.oracle.svm.core.locks.VMMutex; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.monitor.MonitorSupport; @@ -277,14 +277,6 @@ public static void getThreadAllocatedBytes(long[] javaThreadIds, long[] result) } } - static void setInterrupt(Thread thread) { - assert !isVirtual(thread); - if (!JavaThreads.isInterrupted(thread)) { - JavaThreads.writeInterruptedFlag(thread, true); - toTarget(thread).interrupt0(); - } - } - /* End of accessor functions. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -959,19 +951,41 @@ static void unpark(Thread thread) { } } - /** Sleep for the given number of nanoseconds, dealing with early wakeups and interruptions. */ static void sleep(long millis) throws InterruptedException { assert !isCurrentThreadVirtual(); + /* Starting with JDK 19, the thread sleep event is implemented as a Java-level event. */ + if (JavaVersionUtil.JAVA_SPEC >= 19) { + if (com.oracle.svm.core.jfr.HasJfrSupport.get() && Target_jdk_internal_event_ThreadSleepEvent.isTurnedOn()) { + Target_jdk_internal_event_ThreadSleepEvent event = new Target_jdk_internal_event_ThreadSleepEvent(); + try { + event.time = TimeUnit.MILLISECONDS.toNanos(millis); + event.begin(); + sleep0(millis); + } finally { + event.commit(); + } + } else { + sleep0(millis); + } + } else { + long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); + sleep0(millis); + ThreadSleepEventJDK17.emit(millis, startTicks); + } + } + + /** Sleep for the given number of nanoseconds, dealing with early wakeups and interruptions. */ + static void sleep0(long millis) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("Timeout value is negative"); } - sleep0(TimeUtils.millisToNanos(millis)); + sleep1(TimeUtils.millisToNanos(millis)); if (Thread.interrupted()) { // clears the interrupted flag as required of Thread.sleep() throw new InterruptedException(); } } - private static void sleep0(long durationNanos) { + private static void sleep1(long durationNanos) { VMOperationControl.guaranteeOkayToBlock("[PlatformThreads.sleep(long): Should not sleep when it is not okay to block.]"); Thread thread = currentThread.get(); Parker sleepEvent = getCurrentThreadData().ensureSleepParker(); @@ -1038,11 +1052,6 @@ public static int getThreadStatus(Thread thread) { return (JavaVersionUtil.JAVA_SPEC >= 19) ? toTarget(thread).holder.threadStatus : toTarget(thread).threadStatus; } - /** Safe method to get a thread's internal state since {@link Thread#getState} is not final. */ - static Thread.State getThreadState(Thread thread) { - return Target_jdk_internal_misc_VM.toThreadState(getThreadStatus(thread)); - } - public static void setThreadStatus(Thread thread, int threadStatus) { assert !isVirtual(thread); if (JavaVersionUtil.JAVA_SPEC >= 19) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java index 99a0e092c6e0..59caf534584f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java @@ -440,8 +440,6 @@ public static void slowPathSafepointCheck() throws Throwable { @Uninterruptible(reason = "Must not contain safepoint checks") private static void exitSlowPathCheck() { if (ActionOnExitSafepointSupport.isActionPending()) { - // LLVM Backend do not support `FarReturnNode`, - // we explicit specify Loom JDK here. if (LoomSupport.isEnabled() && ActionOnExitSafepointSupport.isSwitchStackPending()) { ActionOnExitSafepointSupport.clearActions(); KnownIntrinsics.farReturn(0, ActionOnExitSafepointSupport.getSwitchStackSP(), ActionOnExitSafepointSupport.getSwitchStackIP(), false); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java deleted file mode 100644 index 72b066101160..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.thread; - -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -import java.util.Locale; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.ContinuationsSupported; -import com.oracle.svm.core.jdk.JDK17OrEarlier; -import com.oracle.svm.core.jdk.NotLoomJDK; -import com.oracle.svm.core.monitor.MonitorInflationCause; -import com.oracle.svm.core.monitor.MonitorSupport; -import com.oracle.svm.core.stack.JavaFrameAnchor; -import com.oracle.svm.core.stack.JavaFrameAnchors; - -import jdk.internal.misc.Unsafe; - -/** - * Implementation of {@link Thread} that does not correspond to an {@linkplain IsolateThread OS - * thread} and instead gets mounted (scheduled to run) on a platform thread (OS thread), - * which, until when it is unmounted again, is called its carrier thread. - * - * This class is based on Project Loom's {@code java.lang.VirtualThread} (8fc1182). - */ -final class SubstrateVirtualThread extends Thread { - private static final Unsafe U = Unsafe.getUnsafe(); - private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler(); - - private static final long STATE = U.objectFieldOffset(SubstrateVirtualThread.class, "state"); - private static final long PARK_PERMIT = U.objectFieldOffset(SubstrateVirtualThread.class, "parkPermit"); - private static final long CARRIER_THREAD = U.objectFieldOffset(SubstrateVirtualThread.class, "carrierThread"); - private static final long TERMINATION = U.objectFieldOffset(SubstrateVirtualThread.class, "termination"); - - // scheduler and continuation - private final Executor scheduler; - private final Continuation cont; - private final Runnable runContinuation; - - private volatile int state; - - private static final int NEW = 0; - private static final int STARTED = 1; - private static final int RUNNABLE = 2; // runnable-unmounted - private static final int RUNNING = 3; // runnable-mounted - private static final int PARKING = 4; - private static final int PARKED = 5; // unmounted - private static final int PINNED = 6; // mounted - private static final int YIELDING = 7; // Thread.yield - private static final int TERMINATED = 99; // final state - - // can be suspended from scheduling when unmounted - private static final int SUSPENDED = 1 << 8; - private static final int RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED); - private static final int PARKED_SUSPENDED = (PARKED | SUSPENDED); - - // parking permit - private volatile boolean parkPermit; - - // carrier thread when mounted - private volatile Thread carrierThread; - - // termination object when joining, created lazily if needed - private volatile CountDownLatch termination; - - /** - * Number of active {@linkplain #pin() pinnings} of this virtual thread to its current carrier - * thread, which prevent it from yielding and therefore unmounting. This is typically needed - * when using or acquiring resources that are associated with the carrier thread. - */ - private short pins; - - SubstrateVirtualThread(Executor scheduler, Runnable task) { - super(task); - if (scheduler == null) { - Thread parent = Thread.currentThread(); - if (parent instanceof SubstrateVirtualThread) { - this.scheduler = ((SubstrateVirtualThread) parent).scheduler; - } else { - this.scheduler = ((SubstrateVirtualThreads) VirtualThreads.singleton()).scheduler; - } - } else { - this.scheduler = scheduler; - } - this.cont = new Continuation(() -> run(task)); - this.runContinuation = this::runContinuation; - } - - private void runContinuation() { - if (Thread.currentThread() instanceof SubstrateVirtualThread) { - throw new RuntimeException("Virtual thread was scheduled on another virtual thread"); - } - - int initialState = state(); - if (initialState == STARTED && compareAndSetState(STARTED, RUNNING)) { - // first run - } else if (initialState == RUNNABLE && compareAndSetState(RUNNABLE, RUNNING)) { - setParkPermit(false); // consume parking permit - } else { - return; // not runnable - } - - try { - cont.enter(); - } finally { - if (cont.isDone()) { - afterTerminate(); - } else { - afterYield(); - } - } - } - - private void submitRunContinuation() { - scheduler.execute(runContinuation); - } - - private void run(Runnable task) { - assert state == RUNNING; - - mount(); - try { - task.run(); - } catch (Throwable exc) { - dispatchUncaughtThrowable(exc); - } finally { - unmount(); - setState(TERMINATED); - } - } - - private void mount() { - Thread carrier = PlatformThreads.currentThread.get(); - setCarrierThread(carrier); - - if (JavaThreads.isInterrupted(this)) { - PlatformThreads.setInterrupt(carrier); - } else if (JavaThreads.isInterrupted(carrier)) { - Object token = acquireInterruptLockMaybeSwitch(); - try { - if (!JavaThreads.isInterrupted(this)) { - JavaThreads.getAndClearInterruptedFlag(carrier); - } - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } - - PlatformThreads.setCurrentThread(carrier, this); - } - - private void unmount() { - Thread carrier = this.carrierThread; - PlatformThreads.setCurrentThread(carrier, carrier); - - // break connection to carrier thread - synchronized (interruptLock()) { // synchronize with interrupt - setCarrierThread(null); - } - JavaThreads.getAndClearInterruptedFlag(carrier); - } - - private boolean yieldContinuation() { - assert this == Thread.currentThread(); - if (isPinned()) { - return false; - } - - unmount(); - try { - return cont.yield() == Continuation.FREEZE_OK; - } finally { - mount(); - } - } - - private void afterYield() { - int s = state(); - assert (s == PARKING || s == YIELDING) && (carrierThread == null); - - if (s == PARKING) { - setState(PARKED); - - // may have been unparked while parking - if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) { - submitRunContinuation(); - } - } else if (s == YIELDING) { // Thread.yield - setState(RUNNABLE); - submitRunContinuation(); - } - } - - private void afterTerminate() { - assert (state() == TERMINATED) && (carrierThread == null); - - // notify anyone waiting for this virtual thread to terminate - @SuppressWarnings("hiding") - CountDownLatch termination = this.termination; - if (termination != null) { - assert termination.getCount() == 1; - termination.countDown(); - } - } - - private void parkOnCarrierThread(boolean timed, long nanos) { - assert state() == PARKING; - - setState(PINNED); - try { - if (!parkPermit) { - if (!timed) { - U.park(false, 0); - } else if (nanos > 0) { - U.park(false, nanos); - } - } - } finally { - setState(RUNNING); - } - setParkPermit(false); // consume - } - - @Override - @SuppressWarnings("sync-override") - public void start() { - if (!compareAndSetState(NEW, STARTED)) { - throw new IllegalThreadStateException("Already started"); - } - - boolean started = false; - try { - submitRunContinuation(); - started = true; - } finally { - if (!started) { - setState(TERMINATED); - afterTerminate(); - } - } - } - - void park() { - assert Thread.currentThread() == this; - - // complete immediately if parking permit available or interrupted - if (getAndSetParkPermit(false) || isInterrupted()) { - return; - } - - // park the thread - setState(PARKING); - try { - if (!yieldContinuation()) { // pinned - parkOnCarrierThread(false, 0); - } - } finally { - assert Thread.currentThread() == this && state() == RUNNING; - } - } - - void parkNanos(long nanos) { - assert Thread.currentThread() == this; - - // complete immediately if parking permit available or interrupted - if (getAndSetParkPermit(false) || isInterrupted()) { - return; - } - - if (nanos > 0) { - long startTime = System.nanoTime(); - - boolean yielded; - Future unparker = scheduleUnpark(nanos); - setState(PARKING); - try { - yielded = yieldContinuation(); - } finally { - assert (Thread.currentThread() == this) && (state() == RUNNING || state() == PARKING); - cancel(unparker); - } - - // park on the carrier thread for remaining time when pinned - if (!yielded) { - long deadline = startTime + nanos; - if (deadline < 0L) { - deadline = Long.MAX_VALUE; - } - parkOnCarrierThread(true, deadline - System.nanoTime()); - } - } - } - - void parkUntil(long deadline) { - long millis = deadline - System.currentTimeMillis(); - long nanos = TimeUnit.NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); - parkNanos(nanos); - } - - private Future scheduleUnpark(long nanos) { - Thread carrier = this.carrierThread; - // need to switch to carrier thread to avoid nested parking - PlatformThreads.setCurrentThread(carrier, carrier); - try { - return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS); - } finally { - PlatformThreads.setCurrentThread(carrier, this); - } - } - - private void cancel(Future future) { - if (!future.isDone()) { - Thread carrier = this.carrierThread; - - // need to switch to carrier thread to avoid nested parking - PlatformThreads.setCurrentThread(carrier, carrier); - try { - future.cancel(false); - } finally { - PlatformThreads.setCurrentThread(carrier, this); - } - } - } - - void unpark() { - Thread currentThread = Thread.currentThread(); - if (!getAndSetParkPermit(true) && currentThread != this) { - int s = state(); - if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) { - if (currentThread instanceof SubstrateVirtualThread) { - SubstrateVirtualThread vthread = (SubstrateVirtualThread) currentThread; - Thread carrier = vthread.carrierThread; - - PlatformThreads.setCurrentThread(carrier, carrier); - try { - submitRunContinuation(); - } finally { - PlatformThreads.setCurrentThread(carrier, vthread); - } - } else { - submitRunContinuation(); - } - } else if (s == PINNED) { - Object token = acquireInterruptLockMaybeSwitch(); - try { - Thread carrier = carrierThread; - if (carrier != null && state() == PINNED) { - Unsafe.getUnsafe().unpark(carrier); - } - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } - } - } - - void tryYield() { - assert Thread.currentThread() == this; - setState(YIELDING); - try { - yieldContinuation(); - } finally { - assert Thread.currentThread() == this; - if (state() != RUNNING) { - assert state() == YIELDING; - setState(RUNNING); - } - } - } - - boolean joinNanos(long nanos) throws InterruptedException { - if (state() == TERMINATED) { - return true; - } - - // ensure termination object exists, then re-check state - @SuppressWarnings("hiding") - CountDownLatch termination = getTermination(); - if (state() == TERMINATED) { - return true; - } - - // wait for virtual thread to terminate - if (nanos == 0) { - termination.await(); - } else { - boolean terminated = termination.await(nanos, NANOSECONDS); - if (!terminated) { - // waiting time elapsed - return false; - } - } - assert state() == TERMINATED; - return true; - } - - private Object interruptLock() { - return JavaThreads.toTarget(this).blockerLock; - } - - /** @see #releaseInterruptLockMaybeSwitchBack */ - private Object acquireInterruptLockMaybeSwitch() { - Object token = null; - if (Thread.currentThread() == this) { - /* - * If we block on our interrupt lock, we yield, for which we first unmount. Unmounting - * also tries to acquire our interrupt lock, so we likely block again, this time on the - * carrier thread. Then, the virtual thread cannot continue to yield, and the carrier - * thread might never get unparked, in which case both threads are stuck. - */ - Thread carrier = carrierThread; - PlatformThreads.setCurrentThread(carrier, carrier); - token = this; - } - MonitorSupport.singleton().monitorEnter(interruptLock(), MonitorInflationCause.VM_INTERNAL); - return token; - } - - /** @see #acquireInterruptLockMaybeSwitch */ - private void releaseInterruptLockMaybeSwitchBack(Object token) { - MonitorSupport.singleton().monitorExit(interruptLock(), MonitorInflationCause.VM_INTERNAL); - if (token != null) { - assert token == this && Thread.currentThread() == carrierThread; - PlatformThreads.setCurrentThread(carrierThread, this); - } - } - - @Override - public void interrupt() { - if (Thread.currentThread() != this) { - Object token = acquireInterruptLockMaybeSwitch(); - try { - JavaThreads.writeInterruptedFlag(this, true); - Target_sun_nio_ch_Interruptible b = JavaThreads.toTarget(this).blocker; - if (b != null) { - b.interrupt(this); - } - Thread carrier = carrierThread; - if (carrier != null) { - PlatformThreads.setInterrupt(carrier); - } - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } else { - JavaThreads.writeInterruptedFlag(this, true); - PlatformThreads.setInterrupt(carrierThread); - } - unpark(); - } - - boolean getAndClearInterrupted() { - assert Thread.currentThread() == this; - Object token = acquireInterruptLockMaybeSwitch(); - try { - boolean oldValue = JavaThreads.getAndClearInterruptedFlag(this); - JavaThreads.getAndClearInterruptedFlag(carrierThread); - return oldValue; - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } - - void sleepNanos(long nanos) throws InterruptedException { - assert Thread.currentThread() == this; - if (nanos >= 0) { - if (JavaThreads.getAndClearInterrupt(this)) { - throw new InterruptedException(); - } - if (nanos == 0) { - tryYield(); - } else { - // park for the sleep time - try { - long remainingNanos = nanos; - long startNanos = System.nanoTime(); - while (remainingNanos > 0) { - parkNanos(remainingNanos); - if (JavaThreads.getAndClearInterrupt(this)) { - throw new InterruptedException(); - } - remainingNanos = nanos - (System.nanoTime() - startNanos); - } - } finally { - // may have been unparked while sleeping - setParkPermit(true); - } - } - } - } - - @Override - public Thread.State getState() { - switch (state()) { - case NEW: - return Thread.State.NEW; - case STARTED: - case RUNNABLE: - case RUNNABLE_SUSPENDED: - // runnable, not mounted - return Thread.State.RUNNABLE; - case RUNNING: - // if mounted then return state of carrier thread - Object token = acquireInterruptLockMaybeSwitch(); - try { - @SuppressWarnings("hiding") - Thread carrierThread = this.carrierThread; - if (carrierThread != null) { - return PlatformThreads.getThreadState(carrierThread); - } - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - // runnable, mounted - return Thread.State.RUNNABLE; - case PARKING: - case YIELDING: - // runnable, mounted, not yet waiting - return Thread.State.RUNNABLE; - case PARKED: - case PARKED_SUSPENDED: - case PINNED: - return Thread.State.WAITING; - case TERMINATED: - return Thread.State.TERMINATED; - default: - throw new InternalError(); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("VirtualThread[#"); - sb.append(JavaThreads.getThreadId(this)); - String name = getName(); - if (!name.isEmpty() && !name.equals("")) { - sb.append(","); - sb.append(name); - } - sb.append("]/"); - Thread carrier = carrierThread; - if (carrier != null) { - // include the carrier thread state and name when mounted - Object token = acquireInterruptLockMaybeSwitch(); - try { - carrier = carrierThread; - if (carrier != null) { - String stateAsString = PlatformThreads.getThreadState(carrier).toString(); - sb.append(stateAsString.toLowerCase(Locale.ROOT)); - sb.append('@'); - sb.append(carrier.getName()); - } - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } - // include virtual thread state when not mounted - if (carrier == null) { - String stateAsString = getState().toString(); - sb.append(stateAsString.toLowerCase(Locale.ROOT)); - } - return sb.toString(); - } - - @Override - public int hashCode() { - return (int) JavaThreads.getThreadId(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - private CountDownLatch getTermination() { - @SuppressWarnings("hiding") - CountDownLatch termination = this.termination; - if (termination == null) { - termination = new CountDownLatch(1); - if (!U.compareAndSetObject(this, TERMINATION, null, termination)) { - termination = this.termination; - } - } - return termination; - } - - private int state() { - return state; // volatile read - } - - private void setState(int newValue) { - state = newValue; // volatile write - } - - private boolean compareAndSetState(int expectedValue, int newValue) { - return U.compareAndSetInt(this, STATE, expectedValue, newValue); - } - - private void setParkPermit(boolean newValue) { - if (parkPermit != newValue) { - parkPermit = newValue; - } - } - - private boolean getAndSetParkPermit(boolean newValue) { - if (parkPermit != newValue) { - return U.getAndSetBoolean(this, PARK_PERMIT, newValue); - } else { - return newValue; - } - } - - private void setCarrierThread(Thread carrier) { - U.putObjectRelease(this, CARRIER_THREAD, carrier); - } - - private void dispatchUncaughtThrowable(Throwable e) { - getUncaughtExceptionHandler().uncaughtException(this, e); - } - - void pin() { - assert currentThread() == this; - assert pins >= 0; - if (pins == Short.MAX_VALUE) { - throw new IllegalStateException("Too many pins"); - } - pins++; - } - - void unpin() { - assert currentThread() == this; - assert pins >= 0; - if (pins == 0) { - throw new IllegalStateException("Not pinned"); - } - pins--; - } - - boolean isPinned() { - assert currentThread() == this; - if (pins > 0) { - return true; - } - JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(); - return anchor.isNonNull() && cont.getBaseSP().aboveThan(anchor.getLastJavaSP()); - } - - @Override - public void run() { - } - - Executor getScheduler() { - return scheduler; - } - - private static ScheduledExecutorService createDelayedTaskScheduler() { - int poolSize = 1; - ScheduledThreadPoolExecutor dts = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(poolSize, - task -> Target_jdk_internal_misc_InnocuousThread.newThread("VirtualThread-unparker", task)); - dts.setRemoveOnCancelPolicy(true); - return dts; - } - - void blockedOn(Target_sun_nio_ch_Interruptible b) { - assert this == Thread.currentThread(); - Object token = acquireInterruptLockMaybeSwitch(); - try { - JavaThreads.toTarget(this).blocker = b; - } finally { - releaseInterruptLockMaybeSwitchBack(token); - } - } - - Pointer getBaseSP() { - Continuation c = cont; - return (c != null) ? c.getBaseSP() : WordFactory.nullPointer(); - } -} - -@TargetClass(className = "jdk.internal.misc.InnocuousThread", onlyWith = {ContinuationsSupported.class, NotLoomJDK.class, JDK17OrEarlier.class}) -final class Target_jdk_internal_misc_InnocuousThread { - @Alias - static native Thread newThread(String name, Runnable target); -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThreads.java deleted file mode 100644 index 27b65e54b4ea..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThreads.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.thread; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinWorkerThread; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import org.graalvm.collections.UnmodifiableEconomicMap; -import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.compiler.options.Option; -import org.graalvm.compiler.options.OptionKey; -import org.graalvm.compiler.options.OptionType; -import org.graalvm.compiler.options.OptionValues; -import org.graalvm.word.Pointer; - -import com.oracle.svm.core.RuntimeAssertionsSupport; -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jdk.StackTraceUtils; -import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.stack.StackFrameVisitor; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; - -/** Our own implementation of virtual threads that does not need Project Loom. */ -public final class SubstrateVirtualThreads implements VirtualThreads { - public static final class Options { - static final int MAX_PARALLELISM = 32767; - - /** - * Default to the maximum number of threads so that we can likely start a platform thread - * for each virtual thread, which we might need when blocking I/O does not yield. - */ - static final int DEFAULT_PARALLELISM = MAX_PARALLELISM; - - @Option(help = "For internal usage. Instead, use the equivalent property 'jdk.virtualThreadScheduler.parallelism' as specified by JEP 425 Virtual Threads (Project Loom).", type = OptionType.Expert) // - public static final HostedOptionKey SubstrateVirtualThreadsParallelism = new HostedOptionKey<>(null) { - @Override - public Integer getValueOrDefault(UnmodifiableEconomicMap, Object> values) { - Integer value = (Integer) values.get(this); - if (value != null) { - UserError.guarantee(value >= 1 && value <= MAX_PARALLELISM, "%s value must be between 1 and %d.", getName(), MAX_PARALLELISM); - return value; - } - String propertyKey = "jdk.virtualThreadScheduler.parallelism"; - String propertyValue = System.getProperty(propertyKey, String.valueOf(DEFAULT_PARALLELISM)); - try { - value = Integer.valueOf(propertyValue); - } catch (NumberFormatException e) { - throw UserError.abort("%s is not a permitted value for %s: must be an integer between 1 and %d.", propertyValue, propertyKey, getName(), MAX_PARALLELISM); - } - return value; - } - - @Override - public Integer getValue(OptionValues values) { - assert checkDescriptorExists(); - return getValueOrDefault(values.getMap()); - } - }; - } - - @Fold - static boolean haveAssertions() { - return RuntimeAssertionsSupport.singleton().desiredAssertionStatus(SubstrateVirtualThreads.class); - } - - private static final class CarrierThread extends ForkJoinWorkerThread { - CarrierThread(ForkJoinPool pool) { - super(pool); - } - - /** - * Ignore any handlers that other code tries to install. {@link #UNCAUGHT_EXCEPTION_HANDLER} - * will still be installed through other means. - */ - @Override - public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { - } - - @Override - protected void onTermination(Throwable exception) { - if (exception != null) { - /* - * Exceptions thrown in virtual threads are caught, but an exception thrown in the - * continuation or virtual thread mechanisms themselves can propagate further. On - * JDK 11, such exceptions terminate a worker thread, cancelling all tasks in its - * queue, and therefore cause liveness problems, so we fail below. - * - * On JDK 17, a worker thread continues executing tasks after catching an exception, - * so we can reach here only if there is an internal exception in ForkJoinPool. - */ - throw VMError.shouldNotReachHere("Carrier thread must not terminate abnormally because it cancels pending tasks " + - "which can result in virtual threads never being scheduled again.", exception); - } - } - } - - /** - * Exceptions thrown in virtual threads are caught, so we reach here only for exceptions thrown - * in the continuation or virtual thread mechanisms themselves. Project Loom does nothing in - * this handler, so we fail only if (system) assertions are on. - * - * NOTE: the caller silently ignores any exception thrown by this method. - * - * @see CarrierThread#onTermination - */ - private static final Thread.UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = (t, e) -> { - if (haveAssertions()) { - throw VMError.shouldNotReachHere("Exception in continuation or virtual thread code", e); - } - }; - - final ForkJoinPool scheduler = new ForkJoinPool(Options.SubstrateVirtualThreadsParallelism.getValue(), CarrierThread::new, UNCAUGHT_EXCEPTION_HANDLER, true); - - private static SubstrateVirtualThread cast(Thread thread) { - return (SubstrateVirtualThread) thread; - } - - private static SubstrateVirtualThread current() { - return (SubstrateVirtualThread) Thread.currentThread(); - } - - @Override - public ThreadFactory createFactory() { - return task -> new SubstrateVirtualThread(null, task); - } - - @Override - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isVirtual(Thread thread) { - return thread instanceof SubstrateVirtualThread; - } - - @Override - public boolean getAndClearInterrupt(Thread thread) { - return cast(thread).getAndClearInterrupted(); - } - - @Override - public void join(Thread thread, long millis) throws InterruptedException { - if (thread.isAlive()) { - long nanos = MILLISECONDS.toNanos(millis); - ((SubstrateVirtualThread) thread).joinNanos(nanos); - } - } - - @Override - public void yield() { - current().tryYield(); - } - - @Override - public void sleepMillis(long millis) throws InterruptedException { - long nanos = TimeUnit.NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); - current().sleepNanos(nanos); - } - - @Override - public boolean isAlive(Thread thread) { - Thread.State state = thread.getState(); - return !(state == Thread.State.NEW || state == Thread.State.TERMINATED); - } - - @Override - public void unpark(Thread thread) { - cast(thread).unpark(); // can throw RejectedExecutionException - } - - @Override - public void park() { - current().park(); - } - - @Override - public void parkNanos(long nanos) { - current().parkNanos(nanos); - } - - @Override - public void parkUntil(long deadline) { - current().parkUntil(deadline); - } - - @Override - public void pinCurrent() { - current().pin(); - } - - @Override - public void unpinCurrent() { - current().unpin(); - } - - @Override - public boolean isCurrentPinned() { - return current().isPinned(); - } - - @Override - public Executor getScheduler(Thread thread) { - return cast(thread).getScheduler(); - } - - @Override - public void blockedOn(Target_sun_nio_ch_Interruptible b) { - current().blockedOn(b); - } - - @Override - public StackTraceElement[] getVirtualOrPlatformThreadStackTrace(boolean filterExceptions, Thread thread, Pointer callerSP) { - if (!isVirtual(thread)) { - return PlatformThreads.getStackTrace(filterExceptions, thread, callerSP); - } - if (thread != Thread.currentThread()) { - return Target_java_lang_Thread.EMPTY_STACK_TRACE; // not implemented - } - Pointer endSP = current().getBaseSP(); - if (endSP.isNull()) { - return null; - } - return StackTraceUtils.getStackTrace(false, callerSP, endSP); - } - - @Override - public void visitCurrentVirtualOrPlatformThreadStackFrames(Pointer callerSP, StackFrameVisitor visitor) { - if (!isVirtual(Thread.currentThread())) { - PlatformThreads.visitCurrentStackFrames(callerSP, visitor); - return; - } - Pointer endSP = current().getBaseSP(); - VMError.guarantee(endSP.isNonNull(), "unexpected null endSP"); - StackTraceUtils.visitCurrentThreadStackFrames(callerSP, endSP, visitor); - } - - @Override - public StackTraceElement[] getVirtualOrPlatformThreadStackTraceAtSafepoint(Thread thread, Pointer callerSP) { - return PlatformThreads.getStackTraceAtSafepoint(thread, callerSP); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index 129fa1a7c82a..3807254e1cf0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -450,7 +450,7 @@ public static boolean interrupted() { * Marks the thread as interrupted and wakes it up. * * See {@link PlatformThreads#parkCurrentPlatformOrCarrierThread}, - * {@link PlatformThreads#unpark} and {@link JavaThreads#sleep} for vital aspects of the + * {@link PlatformThreads#unpark} and {@link PlatformThreads#sleep} for vital aspects of the * underlying mechanisms. */ @Substitute @@ -526,7 +526,7 @@ private int countStackFrames() { @TargetElement(onlyWith = JDK17OrEarlier.class) @Platforms(InternalPlatform.NATIVE_ONLY.class) private boolean isAlive() { - return JavaThreads.isAlive(JavaThreads.fromTarget(this)); + return PlatformThreads.isAlive(JavaThreads.fromTarget(this)); } @Substitute @@ -539,28 +539,28 @@ private boolean isAlive0() { @TargetElement(onlyWith = JDK17OrEarlier.class) @Platforms(InternalPlatform.NATIVE_ONLY.class) private static void yield() { - JavaThreads.yieldCurrent(); + PlatformThreads.singleton().yieldCurrent(); } @Substitute @TargetElement(onlyWith = JDK19OrLater.class) private static void yield0() { - // Loom virtual threads are handled in yield() - JavaThreads.yieldCurrent(); + // Virtual threads are handled in yield() + PlatformThreads.singleton().yieldCurrent(); } @Substitute @TargetElement(onlyWith = JDK17OrEarlier.class) @Platforms(InternalPlatform.NATIVE_ONLY.class) private static void sleep(long millis) throws InterruptedException { - JavaThreads.sleep(millis); + PlatformThreads.sleep(millis); } @Substitute @TargetElement(onlyWith = JDK19OrLater.class) private static void sleep0(long millis) throws InterruptedException { - // Loom virtual threads are handled in sleep() - JavaThreads.sleep(millis); + // Virtual threads are handled in sleep() + PlatformThreads.sleep(millis); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VirtualThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VirtualThreads.java index e57976061249..a098b39e474c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VirtualThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VirtualThreads.java @@ -54,20 +54,6 @@ static boolean isSupported() { void join(Thread thread, long millis) throws InterruptedException; - void yield(); - - void sleepMillis(long millis) throws InterruptedException; - - boolean isAlive(Thread thread); - - void unpark(Thread thread); - - void park(); - - void parkNanos(long nanos); - - void parkUntil(long deadline); - void pinCurrent(); void unpinCurrent();