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