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