< prev index next >

src/java.base/share/classes/java/lang/VirtualThread.java

Print this page

   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 package java.lang;
  26 



  27 import java.util.Locale;
  28 import java.util.Objects;
  29 import java.util.concurrent.Executor;
  30 import java.util.concurrent.Executors;
  31 import java.util.concurrent.ForkJoinPool;
  32 import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
  33 import java.util.concurrent.ForkJoinTask;
  34 import java.util.concurrent.Future;
  35 import java.util.concurrent.RejectedExecutionException;
  36 import java.util.concurrent.ScheduledExecutorService;

  37 import java.util.concurrent.ScheduledThreadPoolExecutor;
  38 import java.util.concurrent.TimeUnit;
  39 import jdk.internal.event.VirtualThreadEndEvent;
  40 import jdk.internal.event.VirtualThreadStartEvent;
  41 import jdk.internal.event.VirtualThreadSubmitFailedEvent;

  42 import jdk.internal.misc.CarrierThread;
  43 import jdk.internal.misc.InnocuousThread;
  44 import jdk.internal.misc.Unsafe;
  45 import jdk.internal.vm.Continuation;
  46 import jdk.internal.vm.ContinuationScope;
  47 import jdk.internal.vm.StackableScope;
  48 import jdk.internal.vm.ThreadContainer;
  49 import jdk.internal.vm.ThreadContainers;
  50 import jdk.internal.vm.annotation.ChangesCurrentThread;
  51 import jdk.internal.vm.annotation.Hidden;
  52 import jdk.internal.vm.annotation.IntrinsicCandidate;
  53 import jdk.internal.vm.annotation.JvmtiHideEvents;
  54 import jdk.internal.vm.annotation.JvmtiMountTransition;
  55 import jdk.internal.vm.annotation.ReservedStackAccess;
  56 import sun.nio.ch.Interruptible;
  57 import static java.util.concurrent.TimeUnit.*;
  58 
  59 /**
  60  * A thread that is scheduled by the Java virtual machine rather than the operating system.
  61  */
  62 final class VirtualThread extends BaseVirtualThread {
  63     private static final Unsafe U = Unsafe.getUnsafe();
  64     private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
  65     private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();






















  66 
  67     private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
  68     private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit");
  69     private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread");
  70     private static final long ON_WAITING_LIST = U.objectFieldOffset(VirtualThread.class, "onWaitingList");
  71 
  72     // scheduler and continuation
  73     private final Executor scheduler;
  74     private final Continuation cont;
  75     private final Runnable runContinuation;
  76 
  77     // virtual thread state, accessed by VM
  78     private volatile int state;
  79 
  80     /*
  81      * Virtual thread state transitions:
  82      *
  83      *      NEW -> STARTED         // Thread.start, schedule to run
  84      *  STARTED -> TERMINATED      // failed to start
  85      *  STARTED -> RUNNING         // first run
  86      *  RUNNING -> TERMINATED      // done
  87      *
  88      *  RUNNING -> PARKING         // Thread parking with LockSupport.park
  89      *  PARKING -> PARKED          // cont.yield successful, parked indefinitely
  90      *   PARKED -> UNPARKED        // unparked, may be scheduled to continue
  91      * UNPARKED -> RUNNING         // continue execution after park
  92      *
  93      *  PARKING -> RUNNING         // cont.yield failed, need to park on carrier
  94      *  RUNNING -> PINNED          // park on carrier
  95      *   PINNED -> RUNNING         // unparked, continue execution on same carrier

 169 
 170     // true when waiting in Object.wait, false for VM internal uninterruptible Object.wait
 171     private volatile boolean interruptibleWait;
 172 
 173     // timed-wait support
 174     private byte timedWaitSeqNo;
 175 
 176     // timeout for timed-park and timed-wait, only accessed on current/carrier thread
 177     private long timeout;
 178 
 179     // timer task for timed-park and timed-wait, only accessed on current/carrier thread
 180     private Future<?> timeoutTask;
 181 
 182     // carrier thread when mounted, accessed by VM
 183     private volatile Thread carrierThread;
 184 
 185     // true to notifyAll after this virtual thread terminates
 186     private volatile boolean notifyAllAfterTerminate;
 187 
 188     /**
 189      * Returns the default scheduler.








 190      */
 191     static Executor defaultScheduler() {
 192         return DEFAULT_SCHEDULER;
 193     }
 194 
 195     /**
 196      * Returns the continuation scope used for virtual threads.
 197      */
 198     static ContinuationScope continuationScope() {
 199         return VTHREAD_SCOPE;
 200     }
 201 
 202     /**
 203      * Creates a new {@code VirtualThread} to run the given task with the given
 204      * scheduler. If the given scheduler is {@code null} and the current thread
 205      * is a platform thread then the newly created virtual thread will use the
 206      * default scheduler. If given scheduler is {@code null} and the current
 207      * thread is a virtual thread then the current thread's scheduler is used.



 208      *
 209      * @param scheduler the scheduler or null

 210      * @param name thread name
 211      * @param characteristics characteristics
 212      * @param task the task to execute
 213      */
 214     VirtualThread(Executor scheduler, String name, int characteristics, Runnable task) {




 215         super(name, characteristics, /*bound*/ false);
 216         Objects.requireNonNull(task);
 217 
 218         // choose scheduler if not specified
 219         if (scheduler == null) {
 220             Thread parent = Thread.currentThread();
 221             if (parent instanceof VirtualThread vparent) {
 222                 scheduler = vparent.scheduler;
 223             } else {
 224                 scheduler = DEFAULT_SCHEDULER;
 225             }
 226         }
 227 
 228         this.scheduler = scheduler;
 229         this.cont = new VThreadContinuation(this, task);
 230         this.runContinuation = this::runContinuation;





























































 231     }
 232 
 233     /**
 234      * The continuation that a virtual thread executes.
 235      */
 236     private static class VThreadContinuation extends Continuation {
 237         VThreadContinuation(VirtualThread vthread, Runnable task) {
 238             super(VTHREAD_SCOPE, wrap(vthread, task));
 239         }
 240         @Override
 241         protected void onPinned(Continuation.Pinned reason) {
 242         }
 243         private static Runnable wrap(VirtualThread vthread, Runnable task) {
 244             return new Runnable() {
 245                 @Hidden
 246                 @JvmtiHideEvents
 247                 public void run() {
 248                     vthread.endFirstTransition();
 249                     try {
 250                         vthread.run(task);

 298             if (cont.isDone()) {
 299                 afterDone();
 300             } else {
 301                 afterYield();
 302             }
 303         }
 304     }
 305 
 306     /**
 307      * Cancel timeout task when continuing after timed-park or timed-wait.
 308      * The timeout task may be executing, or may have already completed.
 309      */
 310     private void cancelTimeoutTask() {
 311         if (timeoutTask != null) {
 312             timeoutTask.cancel(false);
 313             timeoutTask = null;
 314         }
 315     }
 316 
 317     /**
 318      * Submits the given task to the given executor. If the scheduler is a
 319      * ForkJoinPool then the task is first adapted to a ForkJoinTask.
 320      */
 321     private void submit(Executor executor, Runnable task) {
 322         if (executor instanceof ForkJoinPool pool) {
 323             pool.submit(ForkJoinTask.adapt(task));
 324         } else {
 325             executor.execute(task);
 326         }
 327     }
 328 
 329     /**
 330      * Submits the runContinuation task to the scheduler. For the default scheduler,
 331      * and calling it on a worker thread, the task will be pushed to the local queue,
 332      * otherwise it will be pushed to an external submission queue.
 333      * @param scheduler the scheduler
 334      * @param retryOnOOME true to retry indefinitely if OutOfMemoryError is thrown
 335      * @throws RejectedExecutionException
 336      */
 337     private void submitRunContinuation(Executor scheduler, boolean retryOnOOME) {
 338         boolean done = false;
 339         while (!done) {
 340             try {
 341                 // Pin the continuation to prevent the virtual thread from unmounting
 342                 // when submitting a task. For the default scheduler this ensures that
 343                 // the carrier doesn't change when pushing a task. For other schedulers
 344                 // it avoids deadlock that could arise due to carriers and virtual
 345                 // threads contending for a lock.
 346                 if (currentThread().isVirtual()) {
 347                     Continuation.pin();
 348                     try {
 349                         submit(scheduler, runContinuation);
 350                     } finally {
 351                         Continuation.unpin();
 352                     }
 353                 } else {
 354                     submit(scheduler, runContinuation);
 355                 }
 356                 done = true;
 357             } catch (RejectedExecutionException ree) {
 358                 submitFailed(ree);
 359                 throw ree;
 360             } catch (OutOfMemoryError e) {
 361                 if (retryOnOOME) {
 362                     U.park(false, 100_000_000); // 100ms
 363                 } else {
 364                     throw e;
 365                 }
 366             }
 367         }
 368     }
 369 
 370     /**
 371      * Submits the runContinuation task to the given scheduler as an external submit.
 372      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 373      * @throws RejectedExecutionException
 374      * @see ForkJoinPool#externalSubmit(ForkJoinTask)
 375      */
 376     private void externalSubmitRunContinuation(ForkJoinPool pool) {
 377         assert Thread.currentThread() instanceof CarrierThread;
 378         try {
 379             pool.externalSubmit(ForkJoinTask.adapt(runContinuation));
 380         } catch (RejectedExecutionException ree) {
 381             submitFailed(ree);
 382             throw ree;
 383         } catch (OutOfMemoryError e) {
 384             submitRunContinuation(pool, true);
 385         }
 386     }
 387 
 388     /**
 389      * Submits the runContinuation task to the scheduler. For the default scheduler,
 390      * and calling it on a worker thread, the task will be pushed to the local queue,
 391      * otherwise it will be pushed to an external submission queue.
 392      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 393      * @throws RejectedExecutionException
 394      */
 395     private void submitRunContinuation() {
 396         submitRunContinuation(scheduler, true);
 397     }
 398 
 399     /**
 400      * Lazy submit the runContinuation task if invoked on a carrier thread and its local
 401      * queue is empty. If not empty, or invoked by another thread, then this method works
 402      * like submitRunContinuation and just submits the task to the scheduler.
 403      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 404      * @throws RejectedExecutionException
 405      * @see ForkJoinPool#lazySubmit(ForkJoinTask)
 406      */
 407     private void lazySubmitRunContinuation() {

 408         if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
 409             ForkJoinPool pool = ct.getPool();
 410             try {
 411                 pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
 412             } catch (RejectedExecutionException ree) {
 413                 submitFailed(ree);
 414                 throw ree;
 415             } catch (OutOfMemoryError e) {
 416                 submitRunContinuation();
 417             }
 418         } else {
 419             submitRunContinuation();
 420         }
 421     }
 422 
 423     /**
 424      * Submits the runContinuation task to the scheduler. For the default scheduler, and
 425      * calling it a virtual thread that uses the default scheduler, the task will be
 426      * pushed to an external submission queue. This method may throw OutOfMemoryError.

 427      * @throws RejectedExecutionException
 428      * @throws OutOfMemoryError
 429      */
 430     private void externalSubmitRunContinuationOrThrow() {
 431         if (scheduler == DEFAULT_SCHEDULER && currentCarrierThread() instanceof CarrierThread ct) {

 432             try {
 433                 ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation));
 434             } catch (RejectedExecutionException ree) {
 435                 submitFailed(ree);
 436                 throw ree;


 437             }
 438         } else {
 439             submitRunContinuation(scheduler, false);
 440         }
 441     }
 442 
 443     /**
 444      * If enabled, emits a JFR VirtualThreadSubmitFailedEvent.
 445      */
 446     private void submitFailed(RejectedExecutionException ree) {
 447         var event = new VirtualThreadSubmitFailedEvent();
 448         if (event.isEnabled()) {
 449             event.javaThreadId = threadId();
 450             event.exceptionMessage = ree.getMessage();
 451             event.commit();
 452         }
 453     }
 454 
 455     /**
 456      * Runs a task in the context of this virtual thread.
 457      */
 458     private void run(Runnable task) {
 459         assert Thread.currentThread() == this && state == RUNNING;

 576                 long timeout = this.timeout;
 577                 assert timeout > 0;
 578                 timeoutTask = schedule(this::parkTimeoutExpired, timeout, NANOSECONDS);
 579                 setState(newState = TIMED_PARKED);
 580             }
 581 
 582             // may have been unparked while parking
 583             if (parkPermit && compareAndSetState(newState, UNPARKED)) {
 584                 // lazy submit if local queue is empty
 585                 lazySubmitRunContinuation();
 586             }
 587             return;
 588         }
 589 
 590         // Thread.yield
 591         if (s == YIELDING) {
 592             setState(YIELDED);
 593 
 594             // external submit if there are no tasks in the local task queue
 595             if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
 596                 externalSubmitRunContinuation(ct.getPool());
 597             } else {
 598                 submitRunContinuation();
 599             }
 600             return;
 601         }
 602 
 603         // blocking on monitorenter
 604         if (s == BLOCKING) {
 605             setState(BLOCKED);
 606 
 607             // may have been unblocked while blocking
 608             if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) {
 609                 // lazy submit if local queue is empty
 610                 lazySubmitRunContinuation();
 611             }
 612             return;
 613         }
 614 
 615         // Object.wait
 616         if (s == WAITING || s == TIMED_WAITING) {

 701     @Override
 702     void start(ThreadContainer container) {
 703         if (!compareAndSetState(NEW, STARTED)) {
 704             throw new IllegalThreadStateException("Already started");
 705         }
 706 
 707         // bind thread to container
 708         assert threadContainer() == null;
 709         setThreadContainer(container);
 710 
 711         // start thread
 712         boolean addedToContainer = false;
 713         boolean started = false;
 714         try {
 715             container.add(this);  // may throw
 716             addedToContainer = true;
 717 
 718             // scoped values may be inherited
 719             inheritScopedValueBindings(container);
 720 
 721             // submit task to run thread, using externalSubmit if possible
 722             externalSubmitRunContinuationOrThrow();






















 723             started = true;
 724         } finally {
 725             if (!started) {
 726                 afterDone(addedToContainer);
 727             }
 728         }
 729     }
 730 
 731     @Override
 732     public void start() {
 733         start(ThreadContainers.root());
 734     }
 735 
 736     @Override
 737     public void run() {
 738         // do nothing
 739     }
 740 
 741     /**
 742      * Invoked by Thread.join before a thread waits for this virtual thread to terminate.

 857      * Call into VM when pinned to record a JFR jdk.VirtualThreadPinned event.
 858      * Recording the event in the VM avoids having JFR event recorded in Java
 859      * with the same name, but different ID, to events recorded by the VM.
 860      */
 861     @Hidden
 862     private static native void postPinnedEvent(String op);
 863 
 864     /**
 865      * Re-enables this virtual thread for scheduling. If this virtual thread is parked
 866      * then its task is scheduled to continue, otherwise its next call to {@code park} or
 867      * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block.
 868      * @param lazySubmit to use lazySubmit if possible
 869      * @throws RejectedExecutionException if the scheduler cannot accept a task
 870      */
 871     private void unpark(boolean lazySubmit) {
 872         if (!getAndSetParkPermit(true) && currentThread() != this) {
 873             int s = state();
 874 
 875             // unparked while parked
 876             if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) {
 877                 if (lazySubmit) {
 878                     lazySubmitRunContinuation();
 879                 } else {
 880                     submitRunContinuation();













 881                 }


 882                 return;
 883             }
 884 
 885             // unparked while parked when pinned
 886             if (s == PINNED || s == TIMED_PINNED) {
 887                 // unpark carrier thread when pinned
 888                 disableSuspendAndPreempt();
 889                 try {
 890                     synchronized (carrierThreadAccessLock()) {
 891                         Thread carrier = carrierThread;
 892                         if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
 893                             U.unpark(carrier);
 894                         }
 895                     }
 896                 } finally {
 897                     enableSuspendAndPreempt();
 898                 }
 899                 return;
 900             }
 901         }

1308     @IntrinsicCandidate
1309     @JvmtiMountTransition
1310     private native void startTransition(boolean mount);
1311 
1312     @IntrinsicCandidate
1313     @JvmtiMountTransition
1314     private native void endTransition(boolean mount);
1315 
1316     @IntrinsicCandidate
1317     private static native void notifyJvmtiDisableSuspend(boolean enter);
1318 
1319     private static native void registerNatives();
1320     static {
1321         registerNatives();
1322 
1323         // ensure VTHREAD_GROUP is created, may be accessed by JVMTI
1324         var group = Thread.virtualThreadGroup();
1325     }
1326 
1327     /**
1328      * Creates the default ForkJoinPool scheduler.




























1329      */
1330     private static ForkJoinPool createDefaultScheduler() {
1331         ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool);
1332         int parallelism, maxPoolSize, minRunnable;
1333         String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
1334         String maxPoolSizeValue = System.getProperty("jdk.virtualThreadScheduler.maxPoolSize");
1335         String minRunnableValue = System.getProperty("jdk.virtualThreadScheduler.minRunnable");
1336         if (parallelismValue != null) {
1337             parallelism = Integer.parseInt(parallelismValue);
1338         } else {
1339             parallelism = Runtime.getRuntime().availableProcessors();
1340         }
1341         if (maxPoolSizeValue != null) {
1342             maxPoolSize = Integer.parseInt(maxPoolSizeValue);
1343             parallelism = Integer.min(parallelism, maxPoolSize);
1344         } else {
1345             maxPoolSize = Integer.max(parallelism, 256);
1346         }
1347         if (minRunnableValue != null) {
1348             minRunnable = Integer.parseInt(minRunnableValue);
1349         } else {
1350             minRunnable = Integer.max(parallelism / 2, 1);
1351         }
1352         Thread.UncaughtExceptionHandler handler = (t, e) -> { };
1353         boolean asyncMode = true; // FIFO
1354         return new ForkJoinPool(parallelism, factory, handler, asyncMode,
1355                      0, maxPoolSize, minRunnable, pool -> true, 30, SECONDS);











































































1356     }
1357 
1358     /**
1359      * Schedule a runnable task to run after a delay.
1360      */
1361     private Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1362         if (scheduler instanceof ForkJoinPool pool) {
1363             return pool.schedule(command, delay, unit);
1364         } else {
1365             return DelayedTaskSchedulers.schedule(command, delay, unit);


1366         }
1367     }
1368 
1369     /**
1370      * Supports scheduling a runnable task to run after a delay. It uses a number
1371      * of ScheduledThreadPoolExecutor instances to reduce contention on the delayed
1372      * work queue used. This class is used when using a custom scheduler.
1373      */
1374     private static class DelayedTaskSchedulers {
1375         private static final ScheduledExecutorService[] INSTANCE = createDelayedTaskSchedulers();
1376 
1377         static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1378             long tid = Thread.currentThread().threadId();
1379             int index = (int) tid & (INSTANCE.length - 1);
1380             return INSTANCE[index].schedule(command, delay, unit);
1381         }
1382 
1383         private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
1384             String propName = "jdk.virtualThreadScheduler.timerQueues";
1385             String propValue = System.getProperty(propName);
1386             int queueCount;
1387             if (propValue != null) {
1388                 queueCount = Integer.parseInt(propValue);
1389                 if (queueCount != Integer.highestOneBit(queueCount)) {
1390                     throw new RuntimeException("Value of " + propName + " must be power of 2");
1391                 }
1392             } else {
1393                 int ncpus = Runtime.getRuntime().availableProcessors();
1394                 queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);

   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 package java.lang;
  26 
  27 import java.lang.invoke.MethodHandles;
  28 import java.lang.invoke.VarHandle;
  29 import java.lang.reflect.Constructor;
  30 import java.util.Locale;
  31 import java.util.Objects;

  32 import java.util.concurrent.Executors;
  33 import java.util.concurrent.ForkJoinPool;
  34 import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
  35 import java.util.concurrent.ForkJoinTask;
  36 import java.util.concurrent.Future;
  37 import java.util.concurrent.RejectedExecutionException;
  38 import java.util.concurrent.ScheduledExecutorService;
  39 import java.util.concurrent.ScheduledFuture;
  40 import java.util.concurrent.ScheduledThreadPoolExecutor;
  41 import java.util.concurrent.TimeUnit;
  42 import jdk.internal.event.VirtualThreadEndEvent;
  43 import jdk.internal.event.VirtualThreadStartEvent;
  44 import jdk.internal.event.VirtualThreadSubmitFailedEvent;
  45 import jdk.internal.invoke.MhUtil;
  46 import jdk.internal.misc.CarrierThread;
  47 import jdk.internal.misc.InnocuousThread;
  48 import jdk.internal.misc.Unsafe;
  49 import jdk.internal.vm.Continuation;
  50 import jdk.internal.vm.ContinuationScope;
  51 import jdk.internal.vm.StackableScope;
  52 import jdk.internal.vm.ThreadContainer;
  53 import jdk.internal.vm.ThreadContainers;
  54 import jdk.internal.vm.annotation.ChangesCurrentThread;
  55 import jdk.internal.vm.annotation.Hidden;
  56 import jdk.internal.vm.annotation.IntrinsicCandidate;
  57 import jdk.internal.vm.annotation.JvmtiHideEvents;
  58 import jdk.internal.vm.annotation.JvmtiMountTransition;
  59 import jdk.internal.vm.annotation.ReservedStackAccess;
  60 import sun.nio.ch.Interruptible;
  61 import static java.util.concurrent.TimeUnit.*;
  62 
  63 /**
  64  * A thread that is scheduled by the Java virtual machine rather than the operating system.
  65  */
  66 final class VirtualThread extends BaseVirtualThread {
  67     private static final Unsafe U = Unsafe.getUnsafe();
  68     private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
  69 
  70     private static final VirtualThreadScheduler BUILTIN_SCHEDULER;
  71     private static final VirtualThreadScheduler DEFAULT_SCHEDULER;
  72     private static final VirtualThreadScheduler EXTERNAL_VIEW;
  73     private static final boolean USE_STPE;
  74     static {
  75         // experimental
  76         String propValue = System.getProperty("jdk.virtualThreadScheduler.implClass");
  77         if (propValue != null) {
  78             VirtualThreadScheduler builtinScheduler = createBuiltinScheduler(true);
  79             VirtualThreadScheduler externalView = createExternalView(builtinScheduler);
  80             VirtualThreadScheduler defaultScheduler = loadCustomScheduler(externalView, propValue);
  81             BUILTIN_SCHEDULER = builtinScheduler;
  82             DEFAULT_SCHEDULER = defaultScheduler;
  83             EXTERNAL_VIEW = externalView;
  84         } else {
  85             var builtinScheduler = createBuiltinScheduler(false);
  86             BUILTIN_SCHEDULER = builtinScheduler;
  87             DEFAULT_SCHEDULER = builtinScheduler;
  88             EXTERNAL_VIEW = createExternalView(builtinScheduler);
  89         }
  90         USE_STPE = Boolean.getBoolean("jdk.virtualThreadScheduler.useSTPE");
  91     }
  92 
  93     private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
  94     private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit");
  95     private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread");
  96     private static final long ON_WAITING_LIST = U.objectFieldOffset(VirtualThread.class, "onWaitingList");
  97 
  98     // scheduler and continuation
  99     private final VirtualThreadScheduler scheduler;
 100     private final Continuation cont;
 101     private final VThreadTask runContinuation;
 102 
 103     // virtual thread state, accessed by VM
 104     private volatile int state;
 105 
 106     /*
 107      * Virtual thread state transitions:
 108      *
 109      *      NEW -> STARTED         // Thread.start, schedule to run
 110      *  STARTED -> TERMINATED      // failed to start
 111      *  STARTED -> RUNNING         // first run
 112      *  RUNNING -> TERMINATED      // done
 113      *
 114      *  RUNNING -> PARKING         // Thread parking with LockSupport.park
 115      *  PARKING -> PARKED          // cont.yield successful, parked indefinitely
 116      *   PARKED -> UNPARKED        // unparked, may be scheduled to continue
 117      * UNPARKED -> RUNNING         // continue execution after park
 118      *
 119      *  PARKING -> RUNNING         // cont.yield failed, need to park on carrier
 120      *  RUNNING -> PINNED          // park on carrier
 121      *   PINNED -> RUNNING         // unparked, continue execution on same carrier

 195 
 196     // true when waiting in Object.wait, false for VM internal uninterruptible Object.wait
 197     private volatile boolean interruptibleWait;
 198 
 199     // timed-wait support
 200     private byte timedWaitSeqNo;
 201 
 202     // timeout for timed-park and timed-wait, only accessed on current/carrier thread
 203     private long timeout;
 204 
 205     // timer task for timed-park and timed-wait, only accessed on current/carrier thread
 206     private Future<?> timeoutTask;
 207 
 208     // carrier thread when mounted, accessed by VM
 209     private volatile Thread carrierThread;
 210 
 211     // true to notifyAll after this virtual thread terminates
 212     private volatile boolean notifyAllAfterTerminate;
 213 
 214     /**
 215      * Return the built-in scheduler.
 216      * @param trusted true if caller is trusted, false if not trusted
 217      */
 218     static VirtualThreadScheduler builtinScheduler(boolean trusted) {
 219         return trusted ? BUILTIN_SCHEDULER : EXTERNAL_VIEW;
 220     }
 221 
 222     /**
 223      * Returns the default scheduler, usually the same as the built-in scheduler.
 224      */
 225     static VirtualThreadScheduler defaultScheduler() {
 226         return DEFAULT_SCHEDULER;
 227     }
 228 
 229     /**
 230      * Returns the continuation scope used for virtual threads.
 231      */
 232     static ContinuationScope continuationScope() {
 233         return VTHREAD_SCOPE;
 234     }
 235 
 236     /**
 237      * Returns the task to start/continue this virtual thread.
 238      */
 239     VirtualThreadTask virtualThreadTask() {
 240         return runContinuation;
 241     }
 242 
 243     /**
 244      * Creates a new {@code VirtualThread} to run the given task with the given scheduler.
 245      *
 246      * @param scheduler the scheduler or null for default scheduler
 247      * @param preferredCarrier the preferred carrier or null
 248      * @param name thread name
 249      * @param characteristics characteristics
 250      * @param task the task to execute
 251      */
 252     VirtualThread(VirtualThreadScheduler scheduler,
 253                   Thread preferredCarrier,
 254                   String name,
 255                   int characteristics,
 256                   Runnable task) {
 257         super(name, characteristics, /*bound*/ false);
 258         Objects.requireNonNull(task);
 259 
 260         // use default scheduler if not provided
 261         if (scheduler == null) {
 262             scheduler = DEFAULT_SCHEDULER;
 263         } else if (scheduler == EXTERNAL_VIEW) {
 264             throw new UnsupportedOperationException();



 265         }

 266         this.scheduler = scheduler;
 267         this.cont = new VThreadContinuation(this, task);
 268 
 269         if (scheduler == BUILTIN_SCHEDULER) {
 270             this.runContinuation = new VThreadTask(this);
 271         } else {
 272             this.runContinuation = new CustomVThreadTask(this, preferredCarrier);
 273         }
 274     }
 275 
 276     /**
 277      * The task to start/continue a virtual thread.
 278      */
 279     static non-sealed class VThreadTask implements VirtualThreadTask {
 280         private final VirtualThread vthread;
 281         VThreadTask(VirtualThread vthread) {
 282             this.vthread = vthread;
 283         }
 284         @Override
 285         public final Thread thread() {
 286             return vthread;
 287         }
 288         @Override
 289         public final void run() {
 290             vthread.runContinuation();;
 291         }
 292         @Override
 293         public Thread preferredCarrier() {
 294             throw new UnsupportedOperationException();
 295         }
 296         @Override
 297         public Object attach(Object att) {
 298             throw new UnsupportedOperationException();
 299         }
 300         @Override
 301         public Object attachment() {
 302             throw new UnsupportedOperationException();
 303         }
 304     }
 305 
 306     /**
 307      * The task to start/continue a virtual thread when using a custom scheduler.
 308      */
 309     static final class CustomVThreadTask extends VThreadTask {
 310         private static final VarHandle ATT =
 311                 MhUtil.findVarHandle(MethodHandles.lookup(), "att", Object.class);
 312         private final Thread preferredCarrier;
 313         private volatile Object att;
 314         CustomVThreadTask(VirtualThread vthread, Thread preferredCarrier) {
 315             super(vthread);
 316             this.preferredCarrier = preferredCarrier;
 317         }
 318         @Override
 319         public Thread preferredCarrier() {
 320             return preferredCarrier;
 321         }
 322         @Override
 323         public Object attach(Object att) {
 324             return ATT.getAndSet(this, att);
 325         }
 326         @Override
 327         public Object attachment() {
 328             return att;
 329         }
 330     }
 331 
 332     /**
 333      * The continuation that a virtual thread executes.
 334      */
 335     private static class VThreadContinuation extends Continuation {
 336         VThreadContinuation(VirtualThread vthread, Runnable task) {
 337             super(VTHREAD_SCOPE, wrap(vthread, task));
 338         }
 339         @Override
 340         protected void onPinned(Continuation.Pinned reason) {
 341         }
 342         private static Runnable wrap(VirtualThread vthread, Runnable task) {
 343             return new Runnable() {
 344                 @Hidden
 345                 @JvmtiHideEvents
 346                 public void run() {
 347                     vthread.endFirstTransition();
 348                     try {
 349                         vthread.run(task);

 397             if (cont.isDone()) {
 398                 afterDone();
 399             } else {
 400                 afterYield();
 401             }
 402         }
 403     }
 404 
 405     /**
 406      * Cancel timeout task when continuing after timed-park or timed-wait.
 407      * The timeout task may be executing, or may have already completed.
 408      */
 409     private void cancelTimeoutTask() {
 410         if (timeoutTask != null) {
 411             timeoutTask.cancel(false);
 412             timeoutTask = null;
 413         }
 414     }
 415 
 416     /**
 417      * Submits the runContinuation task to the scheduler. For the built-in scheduler,
 418      * the task will be pushed to the local queue if possible, otherwise it will be
 419      * pushed to an external submission queue.













 420      * @param retryOnOOME true to retry indefinitely if OutOfMemoryError is thrown
 421      * @throws RejectedExecutionException
 422      */
 423     private void submitRunContinuation(boolean retryOnOOME) {
 424         boolean done = false;
 425         while (!done) {
 426             try {
 427                 // Pin the continuation to prevent the virtual thread from unmounting
 428                 // when submitting a task. For the default scheduler this ensures that
 429                 // the carrier doesn't change when pushing a task. For other schedulers
 430                 // it avoids deadlock that could arise due to carriers and virtual
 431                 // threads contending for a lock.
 432                 if (currentThread().isVirtual()) {
 433                     Continuation.pin();
 434                     try {
 435                         scheduler.onContinue(runContinuation);
 436                     } finally {
 437                         Continuation.unpin();
 438                     }
 439                 } else {
 440                     scheduler.onContinue(runContinuation);
 441                 }
 442                 done = true;
 443             } catch (RejectedExecutionException ree) {
 444                 submitFailed(ree);
 445                 throw ree;
 446             } catch (OutOfMemoryError e) {
 447                 if (retryOnOOME) {
 448                     U.park(false, 100_000_000); // 100ms
 449                 } else {
 450                     throw e;
 451                 }
 452             }
 453         }
 454     }
 455 


















 456     /**
 457      * Submits the runContinuation task to the scheduler. For the default scheduler,
 458      * and calling it on a worker thread, the task will be pushed to the local queue,
 459      * otherwise it will be pushed to an external submission queue.
 460      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 461      * @throws RejectedExecutionException
 462      */
 463     private void submitRunContinuation() {
 464         submitRunContinuation(true);
 465     }
 466 
 467     /**
 468      * Invoked from a carrier thread to lazy submit the runContinuation task to the
 469      * carrier's local queue if the queue is empty. If not empty, or invoked by a thread
 470      * for a custom scheduler, then it just submits the task to the scheduler.
 471      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 472      * @throws RejectedExecutionException
 473      * @see ForkJoinPool#lazySubmit(ForkJoinTask)
 474      */
 475     private void lazySubmitRunContinuation() {
 476         assert !currentThread().isVirtual();
 477         if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {

 478             try {
 479                 ct.getPool().lazySubmit(ForkJoinTask.adapt(runContinuation));
 480             } catch (RejectedExecutionException ree) {
 481                 submitFailed(ree);
 482                 throw ree;
 483             } catch (OutOfMemoryError e) {
 484                 submitRunContinuation();
 485             }
 486         } else {
 487             submitRunContinuation();
 488         }
 489     }
 490 
 491     /**
 492      * Invoked from a carrier thread to externally submit the runContinuation task to the
 493      * scheduler. If invoked by a thread for a custom scheduler, then it just submits the
 494      * task to the scheduler.
 495      * If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
 496      * @throws RejectedExecutionException
 497      * @see ForkJoinPool#externalSubmit(ForkJoinTask)
 498      */
 499     private void externalSubmitRunContinuation() {
 500         assert !currentThread().isVirtual();
 501         if (currentThread() instanceof CarrierThread ct) {
 502             try {
 503                 ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation));
 504             } catch (RejectedExecutionException ree) {
 505                 submitFailed(ree);
 506                 throw ree;
 507             } catch (OutOfMemoryError e) {
 508                 submitRunContinuation();
 509             }
 510         } else {
 511             submitRunContinuation();
 512         }
 513     }
 514 
 515     /**
 516      * If enabled, emits a JFR VirtualThreadSubmitFailedEvent.
 517      */
 518     private void submitFailed(RejectedExecutionException ree) {
 519         var event = new VirtualThreadSubmitFailedEvent();
 520         if (event.isEnabled()) {
 521             event.javaThreadId = threadId();
 522             event.exceptionMessage = ree.getMessage();
 523             event.commit();
 524         }
 525     }
 526 
 527     /**
 528      * Runs a task in the context of this virtual thread.
 529      */
 530     private void run(Runnable task) {
 531         assert Thread.currentThread() == this && state == RUNNING;

 648                 long timeout = this.timeout;
 649                 assert timeout > 0;
 650                 timeoutTask = schedule(this::parkTimeoutExpired, timeout, NANOSECONDS);
 651                 setState(newState = TIMED_PARKED);
 652             }
 653 
 654             // may have been unparked while parking
 655             if (parkPermit && compareAndSetState(newState, UNPARKED)) {
 656                 // lazy submit if local queue is empty
 657                 lazySubmitRunContinuation();
 658             }
 659             return;
 660         }
 661 
 662         // Thread.yield
 663         if (s == YIELDING) {
 664             setState(YIELDED);
 665 
 666             // external submit if there are no tasks in the local task queue
 667             if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
 668                 externalSubmitRunContinuation();
 669             } else {
 670                 submitRunContinuation();
 671             }
 672             return;
 673         }
 674 
 675         // blocking on monitorenter
 676         if (s == BLOCKING) {
 677             setState(BLOCKED);
 678 
 679             // may have been unblocked while blocking
 680             if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) {
 681                 // lazy submit if local queue is empty
 682                 lazySubmitRunContinuation();
 683             }
 684             return;
 685         }
 686 
 687         // Object.wait
 688         if (s == WAITING || s == TIMED_WAITING) {

 773     @Override
 774     void start(ThreadContainer container) {
 775         if (!compareAndSetState(NEW, STARTED)) {
 776             throw new IllegalThreadStateException("Already started");
 777         }
 778 
 779         // bind thread to container
 780         assert threadContainer() == null;
 781         setThreadContainer(container);
 782 
 783         // start thread
 784         boolean addedToContainer = false;
 785         boolean started = false;
 786         try {
 787             container.add(this);  // may throw
 788             addedToContainer = true;
 789 
 790             // scoped values may be inherited
 791             inheritScopedValueBindings(container);
 792 
 793             // submit task to schedule
 794             try {
 795                 if (currentThread().isVirtual()) {
 796                     Continuation.pin();
 797                     try {
 798                         if (scheduler == BUILTIN_SCHEDULER
 799                                 && currentCarrierThread() instanceof CarrierThread ct) {
 800                             ForkJoinPool pool = ct.getPool();
 801                             ForkJoinTask<?> task = ForkJoinTask.adapt(runContinuation);
 802                             pool.externalSubmit(task);
 803                         } else {
 804                             scheduler.onStart(runContinuation);
 805                         }
 806                     } finally {
 807                         Continuation.unpin();
 808                     }
 809                 } else {
 810                     scheduler.onStart(runContinuation);
 811                 }
 812             } catch (RejectedExecutionException ree) {
 813                 submitFailed(ree);
 814                 throw ree;
 815             }
 816 
 817             started = true;
 818         } finally {
 819             if (!started) {
 820                 afterDone(addedToContainer);
 821             }
 822         }
 823     }
 824 
 825     @Override
 826     public void start() {
 827         start(ThreadContainers.root());
 828     }
 829 
 830     @Override
 831     public void run() {
 832         // do nothing
 833     }
 834 
 835     /**
 836      * Invoked by Thread.join before a thread waits for this virtual thread to terminate.

 951      * Call into VM when pinned to record a JFR jdk.VirtualThreadPinned event.
 952      * Recording the event in the VM avoids having JFR event recorded in Java
 953      * with the same name, but different ID, to events recorded by the VM.
 954      */
 955     @Hidden
 956     private static native void postPinnedEvent(String op);
 957 
 958     /**
 959      * Re-enables this virtual thread for scheduling. If this virtual thread is parked
 960      * then its task is scheduled to continue, otherwise its next call to {@code park} or
 961      * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block.
 962      * @param lazySubmit to use lazySubmit if possible
 963      * @throws RejectedExecutionException if the scheduler cannot accept a task
 964      */
 965     private void unpark(boolean lazySubmit) {
 966         if (!getAndSetParkPermit(true) && currentThread() != this) {
 967             int s = state();
 968 
 969             // unparked while parked
 970             if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) {
 971 
 972                 if (lazySubmit && currentThread().isVirtual()) {
 973                     Continuation.pin();
 974                     try {
 975                         if (scheduler == BUILTIN_SCHEDULER
 976                                 && currentCarrierThread() instanceof CarrierThread ct) {
 977                             ct.getPool().lazySubmit(ForkJoinTask.adapt(runContinuation));
 978                             return;
 979                         }
 980                     } catch (RejectedExecutionException ree) {
 981                         submitFailed(ree);
 982                         throw ree;
 983                     } catch (OutOfMemoryError e) {
 984                         // fall-though
 985                     } finally {
 986                         Continuation.unpin();
 987                     }
 988                 }
 989 
 990                 submitRunContinuation();
 991                 return;
 992             }
 993 
 994             // unparked while parked when pinned
 995             if (s == PINNED || s == TIMED_PINNED) {
 996                 // unpark carrier thread when pinned
 997                 disableSuspendAndPreempt();
 998                 try {
 999                     synchronized (carrierThreadAccessLock()) {
1000                         Thread carrier = carrierThread;
1001                         if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
1002                             U.unpark(carrier);
1003                         }
1004                     }
1005                 } finally {
1006                     enableSuspendAndPreempt();
1007                 }
1008                 return;
1009             }
1010         }

1417     @IntrinsicCandidate
1418     @JvmtiMountTransition
1419     private native void startTransition(boolean mount);
1420 
1421     @IntrinsicCandidate
1422     @JvmtiMountTransition
1423     private native void endTransition(boolean mount);
1424 
1425     @IntrinsicCandidate
1426     private static native void notifyJvmtiDisableSuspend(boolean enter);
1427 
1428     private static native void registerNatives();
1429     static {
1430         registerNatives();
1431 
1432         // ensure VTHREAD_GROUP is created, may be accessed by JVMTI
1433         var group = Thread.virtualThreadGroup();
1434     }
1435 
1436     /**
1437      * Loads a VirtualThreadScheduler with the given class name. The class must be public
1438      * in an exported package, with public one-arg or no-arg constructor, and be visible
1439      * to the system class loader.
1440      * @param delegate the scheduler that the custom scheduler may delegate to
1441      * @param cn the class name of the custom scheduler
1442      */
1443     private static VirtualThreadScheduler loadCustomScheduler(VirtualThreadScheduler delegate, String cn) {
1444         VirtualThreadScheduler scheduler;
1445         try {
1446             Class<?> clazz = Class.forName(cn, true, ClassLoader.getSystemClassLoader());
1447             // 1-arg constructor
1448             try {
1449                 Constructor<?> ctor = clazz.getConstructor(VirtualThreadScheduler.class);
1450                 return (VirtualThreadScheduler) ctor.newInstance(delegate);
1451             } catch (NoSuchMethodException e) {
1452                 // 0-arg constructor
1453                 Constructor<?> ctor = clazz.getConstructor();
1454                 scheduler = (VirtualThreadScheduler) ctor.newInstance();
1455             }
1456         } catch (Exception ex) {
1457             throw new Error(ex);
1458         }
1459         System.err.println("WARNING: Using custom default scheduler, this is an experimental feature!");
1460         return scheduler;
1461     }
1462 
1463     /**
1464      * Creates the built-in ForkJoinPool scheduler.
1465      * @param wrapped true if wrapped by a custom default scheduler
1466      */
1467     private static VirtualThreadScheduler createBuiltinScheduler(boolean wrapped) {

1468         int parallelism, maxPoolSize, minRunnable;
1469         String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
1470         String maxPoolSizeValue = System.getProperty("jdk.virtualThreadScheduler.maxPoolSize");
1471         String minRunnableValue = System.getProperty("jdk.virtualThreadScheduler.minRunnable");
1472         if (parallelismValue != null) {
1473             parallelism = Integer.parseInt(parallelismValue);
1474         } else {
1475             parallelism = Runtime.getRuntime().availableProcessors();
1476         }
1477         if (maxPoolSizeValue != null) {
1478             maxPoolSize = Integer.parseInt(maxPoolSizeValue);
1479             parallelism = Integer.min(parallelism, maxPoolSize);
1480         } else {
1481             maxPoolSize = Integer.max(parallelism, 256);
1482         }
1483         if (minRunnableValue != null) {
1484             minRunnable = Integer.parseInt(minRunnableValue);
1485         } else {
1486             minRunnable = Integer.max(parallelism / 2, 1);
1487         }
1488         ForkJoinWorkerThreadFactory factory;
1489         if (wrapped) {
1490             factory = ForkJoinPool.defaultForkJoinWorkerThreadFactory;
1491         } else {
1492             String cn = System.getProperty("jdk.virtualThreadScheduler.threadFactory");
1493             if (cn != null) {
1494                 try {
1495                     Class<?> clazz = Class.forName(cn, true, ClassLoader.getSystemClassLoader());
1496                     Constructor<?> ctor = clazz.getConstructor();
1497                     factory = (ForkJoinWorkerThreadFactory) ctor.newInstance();
1498                 } catch (Exception ex) {
1499                     throw new Error(ex);
1500                 }
1501             } else {
1502                 factory = CarrierThread::new;
1503             }
1504         }
1505         return new BuiltinForkJoinPoolScheduler(parallelism, maxPoolSize, minRunnable, factory);
1506     }
1507 
1508     /**
1509      * The built-in ForkJoinPool scheduler.
1510      */
1511     private static class BuiltinForkJoinPoolScheduler
1512             extends ForkJoinPool implements VirtualThreadScheduler {
1513 
1514         BuiltinForkJoinPoolScheduler(int parallelism,
1515                                      int maxPoolSize,
1516                                      int minRunnable,
1517                                      ForkJoinWorkerThreadFactory factory) {
1518             Thread.UncaughtExceptionHandler handler = (t, e) -> { };
1519             boolean asyncMode = true; // FIFO
1520             super(parallelism, factory, handler, asyncMode,
1521                     0, maxPoolSize, minRunnable, pool -> true, 30L, SECONDS);
1522         }
1523 
1524         @Override
1525         public void onStart(VirtualThreadTask task) {
1526             execute(ForkJoinTask.adapt(task));
1527         }
1528 
1529         @Override
1530         public void onContinue(VirtualThreadTask task) {
1531             execute(ForkJoinTask.adapt(task));
1532         }
1533 
1534         @Override
1535         public ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit unit) {
1536             return super.schedule(task, delay, unit);
1537         }
1538     }
1539 
1540     /**
1541      * Wraps the scheduler to avoid leaking a direct reference to built-in scheduler.
1542      */
1543     static VirtualThreadScheduler createExternalView(VirtualThreadScheduler delegate) {
1544         return new VirtualThreadScheduler() {
1545             private void check(VirtualThreadTask task) {
1546                 var vthread = (VirtualThread) task.thread();
1547                 VirtualThreadScheduler scheduler = vthread.scheduler;
1548                 if (scheduler != this && scheduler != DEFAULT_SCHEDULER) {
1549                     throw new IllegalArgumentException();
1550                 }
1551             }
1552             @Override
1553             public void onStart(VirtualThreadTask task) {
1554                 check(task);
1555                 delegate.onStart(task);
1556             }
1557             @Override
1558             public void onContinue(VirtualThreadTask task) {
1559                 check(task);
1560                 delegate.onContinue(task);
1561             }
1562             @Override
1563             public String toString() {
1564                 return delegate.toString();
1565             }
1566         };
1567     }
1568 
1569     /**
1570      * Schedule a runnable task to run after a delay.
1571      */
1572     private Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1573         if (USE_STPE) {


1574             return DelayedTaskSchedulers.schedule(command, delay, unit);
1575         } else {
1576             return scheduler.schedule(command, delay, unit);
1577         }
1578     }
1579 
1580     /**
1581      * Supports scheduling a runnable task to run after a delay. It uses a number
1582      * of ScheduledThreadPoolExecutor instances to reduce contention on the delayed
1583      * work queue used. This class is used when using a custom scheduler.
1584      */
1585     static class DelayedTaskSchedulers {
1586         private static final ScheduledExecutorService[] INSTANCE = createDelayedTaskSchedulers();
1587 
1588         static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1589             long tid = Thread.currentThread().threadId();
1590             int index = (int) tid & (INSTANCE.length - 1);
1591             return INSTANCE[index].schedule(command, delay, unit);
1592         }
1593 
1594         private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
1595             String propName = "jdk.virtualThreadScheduler.timerQueues";
1596             String propValue = System.getProperty(propName);
1597             int queueCount;
1598             if (propValue != null) {
1599                 queueCount = Integer.parseInt(propValue);
1600                 if (queueCount != Integer.highestOneBit(queueCount)) {
1601                     throw new RuntimeException("Value of " + propName + " must be power of 2");
1602                 }
1603             } else {
1604                 int ncpus = Runtime.getRuntime().availableProcessors();
1605                 queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);
< prev index next >