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