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