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.lang.invoke.MethodHandles;
 35 import java.lang.invoke.VarHandle;
 36 import java.util.EnumSet;
 37 import java.util.Map;
 38 import java.util.Set;
 39 import java.util.concurrent.ConcurrentHashMap;
 40 import java.util.function.Supplier;
 41 import jdk.internal.access.JavaLangAccess;
 42 import jdk.internal.access.SharedSecrets;
 43 
 44 /**
 45  * A one-shot delimited continuation.
 46  */
 47 public class Continuation {
 48     private static final Unsafe U = Unsafe.getUnsafe();
 49     private static final boolean PRESERVE_EXTENT_LOCAL_CACHE;
 50     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 51     static {
 52         ContinuationSupport.ensureSupported();
 53         PreviewFeatures.ensureEnabled();
 54 
 55         StackChunk.init(); // ensure StackChunk class is initialized
 56 
 57         String value = GetPropertyAction.privilegedGetProperty("jdk.preserveExtentLocalCache");
 58         PRESERVE_EXTENT_LOCAL_CACHE = (value == null) || Boolean.parseBoolean(value);
 59     }
 60 
 61     private static final VarHandle MOUNTED;
 62 
 63     /** Reason for pinning */
 64     public enum Pinned {
 65         /** Native frame on stack */ NATIVE,
 66         /** Monitor held */          MONITOR,
 67         /** In critical section */   CRITICAL_SECTION }
 68 
 69     /** Preemption attempt result */
 70     public enum PreemptStatus {
 71         /** Success */                                                      SUCCESS(null),
 72         /** Permanent failure */                                            PERM_FAIL_UNSUPPORTED(null),
 73         /** Permanent failure: continuation already yielding */             PERM_FAIL_YIELDING(null),
 74         /** Permanent failure: continuation not mounted on the thread */    PERM_FAIL_NOT_MOUNTED(null),
 75         /** Transient failure: continuation pinned due to a held CS */      TRANSIENT_FAIL_PINNED_CRITICAL_SECTION(Pinned.CRITICAL_SECTION),
 76         /** Transient failure: continuation pinned due to native frame */   TRANSIENT_FAIL_PINNED_NATIVE(Pinned.NATIVE),
 77         /** Transient failure: continuation pinned due to a held monitor */ TRANSIENT_FAIL_PINNED_MONITOR(Pinned.MONITOR);
 78 
 79         final Pinned pinned;
 80         private PreemptStatus(Pinned reason) { this.pinned = reason; }
 81         /**
 82          * Whether or not the continuation is pinned.
 83          * @return whether or not the continuation is pinned
 84          **/
 85         public Pinned pinned() { return pinned; }
 86     }
 87 
 88     private static Pinned pinnedReason(int reason) {
 89         return switch (reason) {
 90             case 2 -> Pinned.CRITICAL_SECTION;
 91             case 3 -> Pinned.NATIVE;
 92             case 4 -> Pinned.MONITOR;
 93             default -> throw new AssertionError("Unknown pinned reason: " + reason);
 94         };
 95     }
 96 
 97     private static Thread currentCarrierThread() {
 98         return JLA.currentCarrierThread();
 99     }
100 
101     static {
102         try {
103             registerNatives();
104 
105             // init Pinned to avoid classloading during mounting
106             pinnedReason(2);
107 
108             MethodHandles.Lookup l = MethodHandles.lookup();
109             MOUNTED = l.findVarHandle(Continuation.class, "mounted", boolean.class);
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 = false;
129     private Object yieldInfo;
130     private boolean preempted;
131 
132     private Object[] extentLocalCache;
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.setExtentLocalCache(extentLocalCache);
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_EXTENT_LOCAL_CACHE) {
274                         extentLocalCache = JLA.extentLocalCache();
275                     } else {
276                         extentLocalCache = null;
277                     }
278                     JLA.setExtentLocalCache(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 native static int doYield();
309 
310     @IntrinsicCandidate
311     private native static void enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread);
312 
313 
314     @DontInline
315     @IntrinsicCandidate
316     private static void enter(Continuation c, boolean isContinue) {
317         // This method runs in the "entry frame".
318         // A yield jumps to this method's caller as if returning from this method.
319         try {
320             c.enter0();
321         } finally {
322             c.finish();
323         }
324     }
325 
326     private void enter0() {
327       target.run();
328     }
329 
330     private boolean isStarted() {
331         return tail != null;
332     }
333 
334     private boolean isEmpty() {
335         for (StackChunk c = tail; c != null; c = c.parent()) {
336             if (!c.isEmpty())
337                 return false;
338         }
339         return true;
340     }
341 
342     /**
343      * Suspends the current continuations up to the given scope
344      *
345      * @param scope The {@link ContinuationScope} to suspend
346      * @return {@code true} for success; {@code false} for failure
347      * @throws IllegalStateException if not currently in the given {@code scope},
348      */
349     public static boolean yield(ContinuationScope scope) {
350         Continuation cont = JLA.getContinuation(currentCarrierThread());
351         Continuation c;
352         for (c = cont; c != null && c.scope != scope; c = c.parent)
353             ;
354         if (c == null)
355             throw new IllegalStateException("Not in scope " + scope);
356 
357         return cont.yield0(scope, null);
358     }
359 
360     private boolean yield0(ContinuationScope scope, Continuation child) {
361         preempted = false;
362 
363         if (scope != this.scope)
364             this.yieldInfo = scope;
365         int res = doYield();
366         U.storeFence(); // needed to prevent certain transformations by the compiler
367 
368         assert scope != this.scope || yieldInfo == null : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
369         assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
370 
371         if (child != null) { // TODO: ugly
372             if (res != 0) {
373                 child.yieldInfo = res;
374             } else if (yieldInfo != null) {
375                 assert yieldInfo instanceof Integer;
376                 child.yieldInfo = yieldInfo;
377             } else {
378                 child.yieldInfo = res;
379             }
380             this.yieldInfo = null;
381         } else {
382             if (res == 0 && yieldInfo != null) {
383                 res = (Integer)yieldInfo;
384             }
385             this.yieldInfo = null;
386 
387             if (res == 0)
388                 onContinue();
389             else
390                 onPinned0(res);
391         }
392         assert yieldInfo == null;
393 
394         return res == 0;
395     }
396 
397     private void onPinned0(int reason) {
398         onPinned(pinnedReason(reason));
399     }
400 
401     /**
402      * Called when suspending if the continuation is pinned
403      * @param reason the reason for pinning
404      */
405     protected void onPinned(Pinned reason) {
406         throw new IllegalStateException("Pinned: " + reason);
407     }
408 
409     /**
410      * Called when the continuation continues
411      */
412     protected void onContinue() {
413     }
414 
415     /**
416      * Tests whether this continuation is completed
417      * @return whether this continuation is completed
418      */
419     public boolean isDone() {
420         return done;
421     }
422 
423     /**
424      * Tests whether this unmounted continuation was unmounted by forceful preemption (a successful tryPreempt)
425      * @return whether this unmounted continuation was unmounted by forceful preemption
426      */
427     public boolean isPreempted() {
428         return preempted;
429     }
430 
431     /**
432      * Pins the current continuation (enters a critical section).
433      * This increments an internal semaphore that, when greater than 0, pins the continuation.
434      */
435     public static native void pin();
436 
437     /**
438      * Unpins the current continuation (exits a critical section).
439      * This decrements an internal semaphore that, when equal 0, unpins the current continuation
440      * if pinned with {@link #pin()}.
441      */
442     public static native void unpin();
443 
444     /**
445      * Tests whether the given scope is pinned.
446      * This method is slow.
447      *
448      * @param scope the continuation scope
449      * @return {@code} true if we're in the give scope and are pinned; {@code false otherwise}
450      */
451     public static boolean isPinned(ContinuationScope scope) {
452         int res = isPinned0(scope);
453         return res != 0;
454     }
455 
456     static private native int isPinned0(ContinuationScope scope);
457 
458     private boolean fence() {
459         U.storeFence(); // needed to prevent certain transformations by the compiler
460         return true;
461     }
462 
463     private boolean compareAndSetMounted(boolean expectedValue, boolean newValue) {
464        boolean res = MOUNTED.compareAndSet(this, expectedValue, newValue);
465        return res;
466      }
467 
468     private void setMounted(boolean newValue) {
469         mounted = newValue; // MOUNTED.setVolatile(this, newValue);
470     }
471 
472     private String id() {
473         return Integer.toHexString(System.identityHashCode(this))
474                 + " [" + currentCarrierThread().threadId() + "]";
475     }
476 
477     /**
478      * Tries to forcefully preempt this continuation if it is currently mounted on the given thread
479      * Subclasses may throw an {@link UnsupportedOperationException}, but this does not prevent
480      * the continuation from being preempted on a parent scope.
481      *
482      * @param thread the thread on which to forcefully preempt this continuation
483      * @return the result of the attempt
484      * @throws UnsupportedOperationException if this continuation does not support preemption
485      */
486     public PreemptStatus tryPreempt(Thread thread) {
487         throw new UnsupportedOperationException("Not implemented");
488     }
489 
490     // native methods
491     private static native void registerNatives();
492 
493     private void dump() {
494         System.out.println("Continuation@" + Long.toHexString(System.identityHashCode(this)));
495         System.out.println("\tparent: " + parent);
496         int i = 0;
497         for (StackChunk c = tail; c != null; c = c.parent()) {
498             System.out.println("\tChunk " + i);
499             System.out.println(c);
500         }
501     }
502 
503     private static boolean isEmptyOrTrue(String property) {
504         String value = GetPropertyAction.privilegedGetProperty(property);
505         if (value == null)
506             return false;
507         return value.isEmpty() || Boolean.parseBoolean(value);
508     }
509 }