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