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