1 /*
  2  * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.vm;
 27 
 28 import jdk.internal.misc.PreviewFeatures;
 29 import jdk.internal.misc.Unsafe;
 30 import jdk.internal.vm.annotation.DontInline;
 31 import jdk.internal.vm.annotation.IntrinsicCandidate;
 32 import sun.security.action.GetPropertyAction;
 33 
 34 import java.util.EnumSet;
 35 import java.util.Set;
 36 import java.util.function.Supplier;
 37 import jdk.internal.access.JavaLangAccess;
 38 import jdk.internal.access.SharedSecrets;
 39 
 40 /**
 41  * A one-shot delimited continuation.
 42  */
 43 public class Continuation {
 44     private static final Unsafe U = Unsafe.getUnsafe();
 45     private static final long MOUNTED_OFFSET = U.objectFieldOffset(Continuation.class, "mounted");
 46     private static final boolean PRESERVE_SCOPED_VALUE_CACHE;
 47     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 48     static {
 49         ContinuationSupport.ensureSupported();
 50         PreviewFeatures.ensureEnabled();
 51 
 52         StackChunk.init(); // ensure StackChunk class is initialized
 53 
 54         String value = GetPropertyAction.privilegedGetProperty("jdk.preserveScopedValueCache");
 55         PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value);
 56     }
 57 
 58     /** Reason for pinning */
 59     public enum Pinned {
 60         /** Native frame on stack */ NATIVE,
 61         /** Monitor held */          MONITOR,
 62         /** In critical section */   CRITICAL_SECTION }
 63 
 64     /** Preemption attempt result */
 65     public enum PreemptStatus {
 66         /** Success */                                                      SUCCESS(null),
 67         /** Permanent failure */                                            PERM_FAIL_UNSUPPORTED(null),
 68         /** Permanent failure: continuation already yielding */             PERM_FAIL_YIELDING(null),
 69         /** Permanent failure: continuation not mounted on the thread */    PERM_FAIL_NOT_MOUNTED(null),
 70         /** Transient failure: continuation pinned due to a held CS */      TRANSIENT_FAIL_PINNED_CRITICAL_SECTION(Pinned.CRITICAL_SECTION),
 71         /** Transient failure: continuation pinned due to native frame */   TRANSIENT_FAIL_PINNED_NATIVE(Pinned.NATIVE),
 72         /** Transient failure: continuation pinned due to a held monitor */ TRANSIENT_FAIL_PINNED_MONITOR(Pinned.MONITOR);
 73 
 74         final Pinned pinned;
 75         private PreemptStatus(Pinned reason) { this.pinned = reason; }
 76         /**
 77          * Whether or not the continuation is pinned.
 78          * @return whether or not the continuation is pinned
 79          **/
 80         public Pinned pinned() { return pinned; }
 81     }
 82 
 83     private static Pinned pinnedReason(int reason) {
 84         return switch (reason) {
 85             case 2 -> Pinned.CRITICAL_SECTION;
 86             case 3 -> Pinned.NATIVE;
 87             case 4 -> Pinned.MONITOR;
 88             default -> throw new AssertionError("Unknown pinned reason: " + reason);
 89         };
 90     }
 91 
 92     private static Thread currentCarrierThread() {
 93         return JLA.currentCarrierThread();
 94     }
 95 
 96     static {
 97         try {
 98             registerNatives();
 99 
100             // init Pinned to avoid classloading during mounting
101             pinnedReason(2);
102         } catch (Exception e) {
103             throw new InternalError(e);
104         }
105     }
106 
107     private final Runnable target;
108 
109     /* While the native JVM code is aware that every continuation has a scope, it is, for the most part,
110      * oblivious to the continuation hierarchy. The only time this hierarchy is traversed in native code
111      * is when a hierarchy of continuations is mounted on the native stack.
112      */
113     private final ContinuationScope scope;
114     private Continuation parent; // null for native stack
115     private Continuation child; // non-null when we're yielded in a child continuation
116 
117     private StackChunk tail;
118 
119     private boolean done;
120     private volatile boolean mounted;
121     private Object yieldInfo;
122     private boolean preempted;
123 
124     private Object[] scopedValueCache;
125 
126     /**
127      * Constructs a continuation
128      * @param scope the continuation's scope, used in yield
129      * @param target the continuation's body
130      */
131     public Continuation(ContinuationScope scope, Runnable target) {
132         this.scope = scope;
133         this.target = target;
134     }
135 
136     @Override
137     public String toString() {
138         return super.toString() + " scope: " + scope;
139     }
140 
141     public ContinuationScope getScope() {
142         return scope;
143     }
144 
145     public Continuation getParent() {
146         return parent;
147     }
148 
149     /**
150      * Returns the current innermost continuation with the given scope
151      * @param scope the scope
152      * @return the continuation
153      */
154     public static Continuation getCurrentContinuation(ContinuationScope scope) {
155         Continuation cont = JLA.getContinuation(currentCarrierThread());
156         while (cont != null && cont.scope != scope)
157             cont = cont.parent;
158         return cont;
159     }
160 
161     /**
162      * Creates a StackWalker for this continuation
163      * @return a new StackWalker
164      */
165     public StackWalker stackWalker() {
166         return stackWalker(EnumSet.noneOf(StackWalker.Option.class));
167     }
168 
169     /**
170      * Creates a StackWalker for this continuation
171      * @param options the StackWalker's configuration options
172      * @return a new StackWalker
173      */
174     public StackWalker stackWalker(Set<StackWalker.Option> options) {
175         return stackWalker(options, this.scope);
176     }
177 
178     /**
179      * Creates a StackWalker for this continuation and enclosing ones up to the given scope
180      * @param options the StackWalker's configuration options
181      * @param scope the delimiting continuation scope for the stack
182      * @return a new StackWalker
183      */
184     public StackWalker stackWalker(Set<StackWalker.Option> options, ContinuationScope scope) {
185         return JLA.newStackWalkerInstance(options, scope, innermost());
186     }
187 
188     /**
189      * Obtains a stack trace for this unmounted continuation
190      * @return the stack trace
191      * @throws IllegalStateException if the continuation is mounted
192      */
193     public StackTraceElement[] getStackTrace() {
194         return stackWalker(EnumSet.of(StackWalker.Option.SHOW_REFLECT_FRAMES))
195             .walk(s -> s.map(StackWalker.StackFrame::toStackTraceElement)
196             .toArray(StackTraceElement[]::new));
197     }
198 
199     /// Support for StackWalker
200     public static <R> R wrapWalk(Continuation inner, ContinuationScope scope, Supplier<R> walk) {
201         try {
202             for (Continuation c = inner; c != null && c.scope != scope; c = c.parent)
203                 c.mount();
204             return walk.get();
205         } finally {
206             for (Continuation c = inner; c != null && c.scope != scope; c = c.parent)
207                 c.unmount();
208         }
209     }
210 
211     private Continuation innermost() {
212         Continuation c = this;
213         while (c.child != null)
214             c = c.child;
215         return c;
216     }
217 
218     private void mount() {
219         if (!compareAndSetMounted(false, true))
220             throw new IllegalStateException("Mounted!!!!");
221     }
222 
223     private void unmount() {
224         setMounted(false);
225     }
226 
227     /**
228      * Mounts and runs the continuation body. If suspended, continues it from the last suspend point.
229      */
230     public final void run() {
231         while (true) {
232             mount();
233             JLA.setScopedValueCache(scopedValueCache);
234 
235             if (done)
236                 throw new IllegalStateException("Continuation terminated");
237 
238             Thread t = currentCarrierThread();
239             if (parent != null) {
240                 if (parent != JLA.getContinuation(t))
241                     throw new IllegalStateException();
242             } else
243                 this.parent = JLA.getContinuation(t);
244             JLA.setContinuation(t, this);
245 
246             try {
247                 boolean isVirtualThread = (scope == JLA.virtualThreadContinuationScope());
248                 if (!isStarted()) { // is this the first run? (at this point we know !done)
249                     enterSpecial(this, false, isVirtualThread);
250                 } else {
251                     assert !isEmpty();
252                     enterSpecial(this, true, isVirtualThread);
253                 }
254             } finally {
255                 fence();
256                 try {
257                     assert isEmpty() == done : "empty: " + isEmpty() + " done: " + done + " cont: " + Integer.toHexString(System.identityHashCode(this));
258                     JLA.setContinuation(currentCarrierThread(), this.parent);
259                     if (parent != null)
260                         parent.child = null;
261 
262                     postYieldCleanup();
263 
264                     unmount();
265                     if (PRESERVE_SCOPED_VALUE_CACHE) {
266                         scopedValueCache = JLA.scopedValueCache();
267                     } else {
268                         scopedValueCache = null;
269                     }
270                     JLA.setScopedValueCache(null);
271                 } catch (Throwable e) { e.printStackTrace(); System.exit(1); }
272             }
273             // we're now in the parent continuation
274 
275             assert yieldInfo == null || yieldInfo instanceof ContinuationScope;
276             if (yieldInfo == null || yieldInfo == scope) {
277                 this.parent = null;
278                 this.yieldInfo = null;
279                 return;
280             } else {
281                 parent.child = this;
282                 parent.yield0((ContinuationScope)yieldInfo, this);
283                 parent.child = null;
284             }
285         }
286     }
287 
288     private void postYieldCleanup() {
289         if (done) {
290             this.tail = null;
291         }
292     }
293 
294     private void finish() {
295         done = true;
296         assert isEmpty();
297     }
298 
299     @IntrinsicCandidate
300     private native static int doYield();
301 
302     @IntrinsicCandidate
303     private native static void enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread);
304 
305 
306     @DontInline
307     @IntrinsicCandidate
308     private static void enter(Continuation c, boolean isContinue) {
309         // This method runs in the "entry frame".
310         // A yield jumps to this method's caller as if returning from this method.
311         try {
312             c.enter0();
313         } finally {
314             c.finish();
315         }
316     }
317 
318     private void enter0() {
319       target.run();
320     }
321 
322     private boolean isStarted() {
323         return tail != null;
324     }
325 
326     private boolean isEmpty() {
327         for (StackChunk c = tail; c != null; c = c.parent()) {
328             if (!c.isEmpty())
329                 return false;
330         }
331         return true;
332     }
333 
334     /**
335      * Suspends the current continuations up to the given scope
336      *
337      * @param scope The {@link ContinuationScope} to suspend
338      * @return {@code true} for success; {@code false} for failure
339      * @throws IllegalStateException if not currently in the given {@code scope},
340      */
341     public static boolean yield(ContinuationScope scope) {
342         Continuation cont = JLA.getContinuation(currentCarrierThread());
343         Continuation c;
344         for (c = cont; c != null && c.scope != scope; c = c.parent)
345             ;
346         if (c == null)
347             throw new IllegalStateException("Not in scope " + scope);
348 
349         return cont.yield0(scope, null);
350     }
351 
352     private boolean yield0(ContinuationScope scope, Continuation child) {
353         preempted = false;
354 
355         if (scope != this.scope)
356             this.yieldInfo = scope;
357         int res = doYield();
358         U.storeFence(); // needed to prevent certain transformations by the compiler
359 
360         assert scope != this.scope || yieldInfo == null : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
361         assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
362 
363         if (child != null) { // TODO: ugly
364             if (res != 0) {
365                 child.yieldInfo = res;
366             } else if (yieldInfo != null) {
367                 assert yieldInfo instanceof Integer;
368                 child.yieldInfo = yieldInfo;
369             } else {
370                 child.yieldInfo = res;
371             }
372             this.yieldInfo = null;
373         } else {
374             if (res == 0 && yieldInfo != null) {
375                 res = (Integer)yieldInfo;
376             }
377             this.yieldInfo = null;
378 
379             if (res == 0)
380                 onContinue();
381             else
382                 onPinned0(res);
383         }
384         assert yieldInfo == null;
385 
386         return res == 0;
387     }
388 
389     private void onPinned0(int reason) {
390         onPinned(pinnedReason(reason));
391     }
392 
393     /**
394      * Called when suspending if the continuation is pinned
395      * @param reason the reason for pinning
396      */
397     protected void onPinned(Pinned reason) {
398         throw new IllegalStateException("Pinned: " + reason);
399     }
400 
401     /**
402      * Called when the continuation continues
403      */
404     protected void onContinue() {
405     }
406 
407     /**
408      * Tests whether this continuation is completed
409      * @return whether this continuation is completed
410      */
411     public boolean isDone() {
412         return done;
413     }
414 
415     /**
416      * Tests whether this unmounted continuation was unmounted by forceful preemption (a successful tryPreempt)
417      * @return whether this unmounted continuation was unmounted by forceful preemption
418      */
419     public boolean isPreempted() {
420         return preempted;
421     }
422 
423     /**
424      * Pins the current continuation (enters a critical section).
425      * This increments an internal semaphore that, when greater than 0, pins the continuation.
426      */
427     public static native void pin();
428 
429     /**
430      * Unpins the current continuation (exits a critical section).
431      * This decrements an internal semaphore that, when equal 0, unpins the current continuation
432      * if pinned with {@link #pin()}.
433      */
434     public static native void unpin();
435 
436     /**
437      * Tests whether the given scope is pinned.
438      * This method is slow.
439      *
440      * @param scope the continuation scope
441      * @return {@code} true if we're in the give scope and are pinned; {@code false otherwise}
442      */
443     public static boolean isPinned(ContinuationScope scope) {
444         int res = isPinned0(scope);
445         return res != 0;
446     }
447 
448     static private native int isPinned0(ContinuationScope scope);
449 
450     private boolean fence() {
451         U.storeFence(); // needed to prevent certain transformations by the compiler
452         return true;
453     }
454 
455     private boolean compareAndSetMounted(boolean expectedValue, boolean newValue) {
456         return U.compareAndSetBoolean(this, MOUNTED_OFFSET, expectedValue, newValue);
457     }
458 
459     private void setMounted(boolean newValue) {
460         mounted = newValue; // MOUNTED.setVolatile(this, newValue);
461     }
462 
463     private String id() {
464         return Integer.toHexString(System.identityHashCode(this))
465                 + " [" + currentCarrierThread().threadId() + "]";
466     }
467 
468     /**
469      * Tries to forcefully preempt this continuation if it is currently mounted on the given thread
470      * Subclasses may throw an {@link UnsupportedOperationException}, but this does not prevent
471      * the continuation from being preempted on a parent scope.
472      *
473      * @param thread the thread on which to forcefully preempt this continuation
474      * @return the result of the attempt
475      * @throws UnsupportedOperationException if this continuation does not support preemption
476      */
477     public PreemptStatus tryPreempt(Thread thread) {
478         throw new UnsupportedOperationException("Not implemented");
479     }
480 
481     // native methods
482     private static native void registerNatives();
483 
484     private void dump() {
485         System.out.println("Continuation@" + Long.toHexString(System.identityHashCode(this)));
486         System.out.println("\tparent: " + parent);
487         int i = 0;
488         for (StackChunk c = tail; c != null; c = c.parent()) {
489             System.out.println("\tChunk " + i);
490             System.out.println(c);
491         }
492     }
493 
494     private static boolean isEmptyOrTrue(String property) {
495         String value = GetPropertyAction.privilegedGetProperty(property);
496         if (value == null)
497             return false;
498         return value.isEmpty() || Boolean.parseBoolean(value);
499     }
500 }