| /* | |
| * Copyright (c) 2018, 2024, 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 jdk.internal.vm; | |
| import jdk.internal.misc.Unsafe; | |
| import jdk.internal.vm.annotation.DontInline; | |
| import jdk.internal.vm.annotation.IntrinsicCandidate; | |
| import java.util.EnumSet; | |
| import java.util.Set; | |
| import java.util.function.Supplier; | |
| import jdk.internal.access.JavaLangAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.vm.annotation.Hidden; | |
| import jdk.internal.vm.annotation.JvmtiHideEvents; | |
| /** | |
| * A one-shot delimited continuation. | |
| */ | |
| public class Continuation { | |
| private static final Unsafe U = Unsafe.getUnsafe(); | |
| private static final long MOUNTED_OFFSET = U.objectFieldOffset(Continuation.class, "mounted"); | |
| private static final boolean PRESERVE_SCOPED_VALUE_CACHE; | |
| private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); | |
| static { | |
| ContinuationSupport.ensureSupported(); | |
| StackChunk.init(); // ensure StackChunk class is initialized | |
| String value = System.getProperty("jdk.preserveScopedValueCache"); | |
| PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value); | |
| } | |
| /** Reason for pinning */ | |
| public enum Pinned { | |
| /** Native frame on stack */ NATIVE, | |
| /** Monitor held */ MONITOR, | |
| /** In critical section */ CRITICAL_SECTION, | |
| /** Exception (OOME/SOE) */ EXCEPTION | |
| } | |
| /** Preemption attempt result */ | |
| public enum PreemptStatus { | |
| /** Success */ SUCCESS(null), | |
| /** Permanent failure */ PERM_FAIL_UNSUPPORTED(null), | |
| /** Permanent failure: continuation already yielding */ PERM_FAIL_YIELDING(null), | |
| /** Permanent failure: continuation not mounted on the thread */ PERM_FAIL_NOT_MOUNTED(null), | |
| /** Transient failure: continuation pinned due to a held CS */ TRANSIENT_FAIL_PINNED_CRITICAL_SECTION(Pinned.CRITICAL_SECTION), | |
| /** Transient failure: continuation pinned due to native frame */ TRANSIENT_FAIL_PINNED_NATIVE(Pinned.NATIVE), | |
| /** Transient failure: continuation pinned due to a held monitor */ TRANSIENT_FAIL_PINNED_MONITOR(Pinned.MONITOR); | |
| final Pinned pinned; | |
| private PreemptStatus(Pinned reason) { this.pinned = reason; } | |
| /** | |
| * Whether or not the continuation is pinned. | |
| * @return whether or not the continuation is pinned | |
| **/ | |
| public Pinned pinned() { return pinned; } | |
| } | |
| private static Pinned pinnedReason(int reason) { | |
| return switch (reason) { | |
| case 2 -> Pinned.CRITICAL_SECTION; | |
| case 3 -> Pinned.NATIVE; | |
| case 4 -> Pinned.MONITOR; | |
| case 5 -> Pinned.EXCEPTION; | |
| default -> throw new AssertionError("Unknown pinned reason: " + reason); | |
| }; | |
| } | |
| private static Thread currentCarrierThread() { | |
| return JLA.currentCarrierThread(); | |
| } | |
| static { | |
| try { | |
| registerNatives(); | |
| // init Pinned to avoid classloading during mounting | |
| pinnedReason(2); | |
| } catch (Exception e) { | |
| throw new InternalError(e); | |
| } | |
| } | |
| private final Runnable target; | |
| /* While the native JVM code is aware that every continuation has a scope, it is, for the most part, | |
| * oblivious to the continuation hierarchy. The only time this hierarchy is traversed in native code | |
| * is when a hierarchy of continuations is mounted on the native stack. | |
| */ | |
| private final ContinuationScope scope; | |
| private Continuation parent; // null for native stack | |
| private Continuation child; // non-null when we're yielded in a child continuation | |
| private StackChunk tail; | |
| private boolean done; | |
| private volatile boolean mounted; | |
| private Object yieldInfo; | |
| private boolean preempted; | |
| private Object[] scopedValueCache; | |
| /** | |
| * Constructs a continuation | |
| * @param scope the continuation's scope, used in yield | |
| * @param target the continuation's body | |
| */ | |
| public Continuation(ContinuationScope scope, Runnable target) { | |
| this.scope = scope; | |
| this.target = target; | |
| } | |
| @Override | |
| public String toString() { | |
| return super.toString() + " scope: " + scope; | |
| } | |
| public ContinuationScope getScope() { | |
| return scope; | |
| } | |
| public Continuation getParent() { | |
| return parent; | |
| } | |
| /** | |
| * Returns the current innermost continuation with the given scope | |
| * @param scope the scope | |
| * @return the continuation | |
| */ | |
| public static Continuation getCurrentContinuation(ContinuationScope scope) { | |
| Continuation cont = JLA.getContinuation(currentCarrierThread()); | |
| while (cont != null && cont.scope != scope) | |
| cont = cont.parent; | |
| return cont; | |
| } | |
| /** | |
| * Creates a StackWalker for this continuation | |
| * @return a new StackWalker | |
| */ | |
| public StackWalker stackWalker() { | |
| return stackWalker(EnumSet.noneOf(StackWalker.Option.class)); | |
| } | |
| /** | |
| * Creates a StackWalker for this continuation | |
| * @param options the StackWalker's configuration options | |
| * @return a new StackWalker | |
| */ | |
| public StackWalker stackWalker(Set<StackWalker.Option> options) { | |
| return stackWalker(options, this.scope); | |
| } | |
| /** | |
| * Creates a StackWalker for this continuation and enclosing ones up to the given scope | |
| * @param options the StackWalker's configuration options | |
| * @param scope the delimiting continuation scope for the stack | |
| * @return a new StackWalker | |
| */ | |
| public StackWalker stackWalker(Set<StackWalker.Option> options, ContinuationScope scope) { | |
| return JLA.newStackWalkerInstance(options, scope, innermost()); | |
| } | |
| /** | |
| * Obtains a stack trace for this unmounted continuation | |
| * @return the stack trace | |
| * @throws IllegalStateException if the continuation is mounted | |
| */ | |
| public StackTraceElement[] getStackTrace() { | |
| return stackWalker(EnumSet.of(StackWalker.Option.SHOW_REFLECT_FRAMES)) | |
| .walk(s -> s.map(StackWalker.StackFrame::toStackTraceElement) | |
| .toArray(StackTraceElement[]::new)); | |
| } | |
| /// Support for StackWalker | |
| public static <R> R wrapWalk(Continuation inner, ContinuationScope scope, Supplier<R> walk) { | |
| try { | |
| for (Continuation c = inner; c != null && c.scope != scope; c = c.parent) | |
| c.mount(); | |
| return walk.get(); | |
| } finally { | |
| for (Continuation c = inner; c != null && c.scope != scope; c = c.parent) | |
| c.unmount(); | |
| } | |
| } | |
| private Continuation innermost() { | |
| Continuation c = this; | |
| while (c.child != null) | |
| c = c.child; | |
| return c; | |
| } | |
| private void mount() { | |
| if (!compareAndSetMounted(false, true)) | |
| throw new IllegalStateException("Mounted!!!!"); | |
| } | |
| private void unmount() { | |
| setMounted(false); | |
| } | |
| /** | |
| * Mounts and runs the continuation body. If suspended, continues it from the last suspend point. | |
| */ | |
| public final void run() { | |
| while (true) { | |
| mount(); | |
| JLA.setScopedValueCache(scopedValueCache); | |
| if (done) | |
| throw new IllegalStateException("Continuation terminated"); | |
| Thread t = currentCarrierThread(); | |
| if (parent != null) { | |
| if (parent != JLA.getContinuation(t)) | |
| throw new IllegalStateException(); | |
| } else | |
| this.parent = JLA.getContinuation(t); | |
| JLA.setContinuation(t, this); | |
| try { | |
| boolean isVirtualThread = (scope == JLA.virtualThreadContinuationScope()); | |
| if (!isStarted()) { // is this the first run? (at this point we know !done) | |
| enterSpecial(this, false, isVirtualThread); | |
| } else { | |
| assert !isEmpty(); | |
| enterSpecial(this, true, isVirtualThread); | |
| } | |
| } finally { | |
| fence(); | |
| try { | |
| assert isEmpty() == done : "empty: " + isEmpty() + " done: " + done + " cont: " + Integer.toHexString(System.identityHashCode(this)); | |
| JLA.setContinuation(currentCarrierThread(), this.parent); | |
| if (parent != null) | |
| parent.child = null; | |
| postYieldCleanup(); | |
| unmount(); | |
| if (PRESERVE_SCOPED_VALUE_CACHE) { | |
| scopedValueCache = JLA.scopedValueCache(); | |
| } else { | |
| scopedValueCache = null; | |
| } | |
| JLA.setScopedValueCache(null); | |
| } catch (Throwable e) { e.printStackTrace(); System.exit(1); } | |
| } | |
| // we're now in the parent continuation | |
| assert yieldInfo == null || yieldInfo instanceof ContinuationScope; | |
| if (yieldInfo == null || yieldInfo == scope) { | |
| this.parent = null; | |
| this.yieldInfo = null; | |
| return; | |
| } else { | |
| parent.child = this; | |
| parent.yield0((ContinuationScope)yieldInfo, this); | |
| parent.child = null; | |
| } | |
| } | |
| } | |
| private void postYieldCleanup() { | |
| if (done) { | |
| this.tail = null; | |
| } | |
| } | |
| private void finish() { | |
| done = true; | |
| assert isEmpty(); | |
| } | |
| @IntrinsicCandidate | |
| private static native int doYield(); | |
| @IntrinsicCandidate | |
| private static native void enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread); | |
| @Hidden | |
| @DontInline | |
| @IntrinsicCandidate | |
| @JvmtiHideEvents | |
| private static void enter(Continuation c, boolean isContinue) { | |
| // This method runs in the "entry frame". | |
| // A yield jumps to this method's caller as if returning from this method. | |
| try { | |
| c.enter0(); | |
| } finally { | |
| c.finish(); | |
| } | |
| } | |
| @Hidden | |
| @JvmtiHideEvents | |
| private void enter0() { | |
| target.run(); | |
| } | |
| private boolean isStarted() { | |
| return tail != null; | |
| } | |
| private boolean isEmpty() { | |
| for (StackChunk c = tail; c != null; c = c.parent()) { | |
| if (!c.isEmpty()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Suspends the current continuations up to the given scope | |
| * | |
| * @param scope The {@link ContinuationScope} to suspend | |
| * @return {@code true} for success; {@code false} for failure | |
| * @throws IllegalStateException if not currently in the given {@code scope}, | |
| */ | |
| @Hidden | |
| @JvmtiHideEvents | |
| public static boolean yield(ContinuationScope scope) { | |
| Continuation cont = JLA.getContinuation(currentCarrierThread()); | |
| Continuation c; | |
| for (c = cont; c != null && c.scope != scope; c = c.parent) | |
| ; | |
| if (c == null) | |
| throw new IllegalStateException("Not in scope " + scope); | |
| return cont.yield0(scope, null); | |
| } | |
| @Hidden | |
| @JvmtiHideEvents | |
| private boolean yield0(ContinuationScope scope, Continuation child) { | |
| if (scope != this.scope) | |
| this.yieldInfo = scope; | |
| int res = doYield(); | |
| U.storeFence(); // needed to prevent certain transformations by the compiler | |
| assert scope != this.scope || yieldInfo == null : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; | |
| assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; | |
| if (child != null) { // TODO: ugly | |
| if (res != 0) { | |
| child.yieldInfo = res; | |
| } else if (yieldInfo != null) { | |
| assert yieldInfo instanceof Integer; | |
| child.yieldInfo = yieldInfo; | |
| } else { | |
| child.yieldInfo = res; | |
| } | |
| this.yieldInfo = null; | |
| } else { | |
| if (res == 0 && yieldInfo != null) { | |
| res = (Integer)yieldInfo; | |
| } | |
| this.yieldInfo = null; | |
| if (res == 0) | |
| onContinue(); | |
| else | |
| onPinned0(res); | |
| } | |
| assert yieldInfo == null; | |
| return res == 0; | |
| } | |
| private void onPinned0(int reason) { | |
| onPinned(pinnedReason(reason)); | |
| } | |
| /** | |
| * Called when suspending if the continuation is pinned | |
| * @param reason the reason for pinning | |
| */ | |
| protected void onPinned(Pinned reason) { | |
| throw new IllegalStateException("Pinned: " + reason); | |
| } | |
| /** | |
| * Called when the continuation continues | |
| */ | |
| protected void onContinue() { | |
| } | |
| /** | |
| * Tests whether this continuation is completed | |
| * @return whether this continuation is completed | |
| */ | |
| public boolean isDone() { | |
| return done; | |
| } | |
| /** | |
| * Tests whether this unmounted continuation was unmounted by forceful preemption (a successful tryPreempt) | |
| * @return whether this unmounted continuation was unmounted by forceful preemption | |
| */ | |
| public boolean isPreempted() { | |
| return preempted; | |
| } | |
| /** | |
| * Pins the current continuation (enters a critical section). | |
| * This increments an internal semaphore that, when greater than 0, pins the continuation. | |
| */ | |
| @IntrinsicCandidate | |
| public static native void pin(); | |
| /** | |
| * Unpins the current continuation (exits a critical section). | |
| * This decrements an internal semaphore that, when equal 0, unpins the current continuation | |
| * if pinned with {@link #pin()}. | |
| */ | |
| @IntrinsicCandidate | |
| public static native void unpin(); | |
| /** | |
| * Tests whether the given scope is pinned. | |
| * This method is slow. | |
| * | |
| * @param scope the continuation scope | |
| * @return {@code} true if we're in the give scope and are pinned; {@code false otherwise} | |
| */ | |
| public static boolean isPinned(ContinuationScope scope) { | |
| int res = isPinned0(scope); | |
| return res != 0; | |
| } | |
| private static native int isPinned0(ContinuationScope scope); | |
| private boolean fence() { | |
| U.storeFence(); // needed to prevent certain transformations by the compiler | |
| return true; | |
| } | |
| private boolean compareAndSetMounted(boolean expectedValue, boolean newValue) { | |
| return U.compareAndSetBoolean(this, MOUNTED_OFFSET, expectedValue, newValue); | |
| } | |
| private void setMounted(boolean newValue) { | |
| mounted = newValue; // MOUNTED.setVolatile(this, newValue); | |
| } | |
| private String id() { | |
| return Integer.toHexString(System.identityHashCode(this)) | |
| + " [" + currentCarrierThread().threadId() + "]"; | |
| } | |
| /** | |
| * Tries to forcefully preempt this continuation if it is currently mounted on the given thread | |
| * Subclasses may throw an {@link UnsupportedOperationException}, but this does not prevent | |
| * the continuation from being preempted on a parent scope. | |
| * | |
| * @param thread the thread on which to forcefully preempt this continuation | |
| * @return the result of the attempt | |
| * @throws UnsupportedOperationException if this continuation does not support preemption | |
| */ | |
| public PreemptStatus tryPreempt(Thread thread) { | |
| throw new UnsupportedOperationException("Not implemented"); | |
| } | |
| // native methods | |
| private static native void registerNatives(); | |
| private void dump() { | |
| System.out.println("Continuation@" + Long.toHexString(System.identityHashCode(this))); | |
| System.out.println("\tparent: " + parent); | |
| int i = 0; | |
| for (StackChunk c = tail; c != null; c = c.parent()) { | |
| System.out.println("\tChunk " + i); | |
| System.out.println(c); | |
| } | |
| } | |
| private static boolean isEmptyOrTrue(String property) { | |
| String value = System.getProperty(property); | |
| if (value == null) | |
| return false; | |
| return value.isEmpty() || Boolean.parseBoolean(value); | |
| } | |
| } |