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