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

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



 272         }

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

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













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


















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

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

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

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

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

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

1475         int parallelism, maxPoolSize, minRunnable;
1476         String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
1477         String maxPoolSizeValue = System.getProperty("jdk.virtualThreadScheduler.maxPoolSize");
1478         String minRunnableValue = System.getProperty("jdk.virtualThreadScheduler.minRunnable");
1479         if (parallelismValue != null) {
1480             parallelism = Integer.parseInt(parallelismValue);
1481         } else {
1482             parallelism = Runtime.getRuntime().availableProcessors();
1483         }
1484         if (maxPoolSizeValue != null) {
1485             maxPoolSize = Integer.parseInt(maxPoolSizeValue);
1486             parallelism = Integer.min(parallelism, maxPoolSize);
1487         } else {
1488             maxPoolSize = Integer.max(parallelism, 256);
1489         }
1490         if (minRunnableValue != null) {
1491             minRunnable = Integer.parseInt(minRunnableValue);
1492         } else {
1493             minRunnable = Integer.max(parallelism / 2, 1);
1494         }
1495         if (Boolean.getBoolean("jdk.virtualThreadScheduler.useTPE")) {
1496             return new BuiltinThreadPoolExecutorScheduler(parallelism);
1497         } else {
1498             return new BuiltinForkJoinPoolScheduler(parallelism, maxPoolSize, minRunnable, wrapped);
1499         }
1500     }
1501 
1502     /**
1503      * The built-in ForkJoinPool scheduler.
1504      */
1505     private static class BuiltinForkJoinPoolScheduler
1506             extends ForkJoinPool implements VirtualThreadScheduler {
1507 
1508         BuiltinForkJoinPoolScheduler(int parallelism, int maxPoolSize, int minRunnable, boolean wrapped) {
1509             ForkJoinWorkerThreadFactory factory = wrapped
1510                     ? ForkJoinPool.defaultForkJoinWorkerThreadFactory
1511                     : CarrierThread::new;
1512             Thread.UncaughtExceptionHandler handler = (t, e) -> { };
1513             boolean asyncMode = true; // FIFO
1514             super(parallelism, factory, handler, asyncMode,
1515                     0, maxPoolSize, minRunnable, pool -> true, 30L, SECONDS);
1516         }
1517 
1518         @Override
1519         public void onStart(VirtualThreadTask task) {
1520             execute(ForkJoinTask.adapt(task));
1521         }
1522 
1523         @Override
1524         public void onContinue(VirtualThreadTask task) {
1525             execute(ForkJoinTask.adapt(task));
1526         }
1527 
1528         @Override
1529         public ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit unit) {
1530             return super.schedule(task, delay, unit);
1531         }
1532     }
1533 
1534     /**
1535      * Built-in ThreadPoolExecutor scheduler.
1536      */
1537     private static class BuiltinThreadPoolExecutorScheduler
1538             extends ThreadPoolExecutor implements VirtualThreadScheduler {
1539 
1540         BuiltinThreadPoolExecutorScheduler(int maxPoolSize) {
1541             ThreadFactory factory = task -> {
1542                 Thread t = InnocuousThread.newThread(task);
1543                 t.setDaemon(true);
1544                 return t;
1545             };
1546             super(maxPoolSize, maxPoolSize,
1547                     0L, SECONDS,
1548                     new LinkedTransferQueue<>(),
1549                     factory);
1550         }
1551 
1552         @Override
1553         public void onStart(VirtualThreadTask task) {
1554             execute(task);
1555         }
1556 
1557         @Override
1558         public void onContinue(VirtualThreadTask task) {
1559             execute(task);
1560         }
1561     }
1562 
1563     /**
1564      * Wraps the scheduler to avoid leaking a direct reference to built-in scheduler.
1565      */
1566     static VirtualThreadScheduler createExternalView(VirtualThreadScheduler delegate) {
1567         return new VirtualThreadScheduler() {
1568             private void check(VirtualThreadTask task) {
1569                 var vthread = (VirtualThread) task.thread();
1570                 VirtualThreadScheduler scheduler = vthread.scheduler;
1571                 if (scheduler != this && scheduler != DEFAULT_SCHEDULER) {
1572                     throw new IllegalArgumentException();
1573                 }
1574             }
1575             @Override
1576             public void onStart(VirtualThreadTask task) {
1577                 check(task);
1578                 delegate.onStart(task);
1579             }
1580             @Override
1581             public void onContinue(VirtualThreadTask task) {
1582                 check(task);
1583                 delegate.onContinue(task);
1584             }
1585             @Override
1586             public String toString() {
1587                 return delegate.toString();
1588             }
1589         };
1590     }
1591 
1592     /**
1593      * Schedule a runnable task to run after a delay.
1594      */
1595     private Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1596         if (USE_STPE) {


1597             return DelayedTaskSchedulers.schedule(command, delay, unit);
1598         } else {
1599             return scheduler.schedule(command, delay, unit);
1600         }
1601     }
1602 
1603     /**
1604      * Supports scheduling a runnable task to run after a delay. It uses a number
1605      * of ScheduledThreadPoolExecutor instances to reduce contention on the delayed
1606      * work queue used. This class is used when using a custom scheduler.
1607      */
1608     static class DelayedTaskSchedulers {
1609         private static final ScheduledExecutorService[] INSTANCE = createDelayedTaskSchedulers();
1610 
1611         static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
1612             long tid = Thread.currentThread().threadId();
1613             int index = (int) tid & (INSTANCE.length - 1);
1614             return INSTANCE[index].schedule(command, delay, unit);
1615         }
1616 
1617         private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
1618             String propName = "jdk.virtualThreadScheduler.timerQueues";
1619             String propValue = System.getProperty(propName);
1620             int queueCount;
1621             if (propValue != null) {
1622                 queueCount = Integer.parseInt(propValue);
1623                 if (queueCount != Integer.highestOneBit(queueCount)) {
1624                     throw new RuntimeException("Value of " + propName + " must be power of 2");
1625                 }
1626             } else {
1627                 int ncpus = Runtime.getRuntime().availableProcessors();
1628                 queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);
< prev index next >