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