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