1 /*
   2  * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test id=default
  26  * @bug 8284161 8286788 8321270
  27  * @summary Test Thread API with virtual threads
  28  * @modules java.base/java.lang:+open
  29  * @library /test/lib
  30  * @run junit ThreadAPI
  31  */
  32 
  33 /*
  34  * @test id=no-vmcontinuations
  35  * @requires vm.continuations
  36  * @modules java.base/java.lang:+open
  37  * @library /test/lib
  38  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ThreadAPI
  39  */
  40 
  41 import java.time.Duration;
  42 import java.util.Arrays;
  43 import java.util.ArrayList;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.Set;
  47 import java.util.concurrent.CopyOnWriteArrayList;
  48 import java.util.concurrent.CountDownLatch;
  49 import java.util.concurrent.ExecutorService;
  50 import java.util.concurrent.Executor;
  51 import java.util.concurrent.Executors;
  52 import java.util.concurrent.ForkJoinPool;
  53 import java.util.concurrent.ScheduledExecutorService;
  54 import java.util.concurrent.ThreadFactory;
  55 import java.util.concurrent.TimeUnit;
  56 import java.util.concurrent.atomic.AtomicBoolean;
  57 import java.util.concurrent.atomic.AtomicReference;
  58 import java.util.concurrent.locks.LockSupport;
  59 import java.util.concurrent.locks.ReentrantLock;
  60 import java.util.stream.Stream;
  61 import java.nio.channels.Selector;
  62 
  63 import jdk.test.lib.thread.VThreadRunner;
  64 import org.junit.jupiter.api.Test;
  65 import org.junit.jupiter.api.BeforeAll;
  66 import org.junit.jupiter.api.AfterAll;
  67 import org.junit.jupiter.params.ParameterizedTest;
  68 import org.junit.jupiter.params.provider.MethodSource;
  69 import static org.junit.jupiter.api.Assertions.*;
  70 import static org.junit.jupiter.api.Assumptions.*;
  71 
  72 class ThreadAPI {
  73     private static final Object lock = new Object();
  74 
  75     // used for scheduling thread interrupt
  76     private static ScheduledExecutorService scheduler;
  77 
  78     @BeforeAll
  79     static void setup() throws Exception {
  80         ThreadFactory factory = Executors.defaultThreadFactory();
  81         scheduler = Executors.newSingleThreadScheduledExecutor(factory);
  82     }
  83 
  84     @AfterAll
  85     static void finish() {
  86         scheduler.shutdown();
  87     }
  88 
  89     /**
  90      * An operation that does not return a result but may throw an exception.
  91      */
  92     @FunctionalInterface
  93     interface ThrowingRunnable {
  94         void run() throws Exception;
  95     }
  96 
  97     /**
  98      * Test Thread.currentThread before/after park.
  99      */
 100     @Test
 101     void testCurrentThread1() throws Exception {
 102         var before = new AtomicReference<Thread>();
 103         var after = new AtomicReference<Thread>();
 104         var thread = Thread.ofVirtual().start(() -> {
 105             before.set(Thread.currentThread());
 106             LockSupport.park();
 107             after.set(Thread.currentThread());
 108         });
 109         await(thread, Thread.State.WAITING);
 110         LockSupport.unpark(thread);
 111         thread.join();
 112         assertTrue(before.get() == thread);
 113         assertTrue(after.get() == thread);
 114     }
 115 
 116     /**
 117      * Test Thread.currentThread before/after entering synchronized block.
 118      */
 119     @Test
 120     void testCurrentThread2() throws Exception {
 121         var ref1 = new AtomicReference<Thread>();
 122         var ref2 = new AtomicReference<Thread>();
 123         var ref3 = new AtomicReference<Thread>();
 124         var thread = Thread.ofVirtual().unstarted(() -> {
 125             ref1.set(Thread.currentThread());
 126             synchronized (lock) {
 127                 ref2.set(Thread.currentThread());
 128             }
 129             ref3.set(Thread.currentThread());
 130         });
 131         synchronized (lock) {
 132             thread.start();
 133             await(thread, Thread.State.BLOCKED);
 134         }
 135         thread.join();
 136         assertTrue(ref1.get() == thread);
 137         assertTrue(ref2.get() == thread);
 138         assertTrue(ref3.get() == thread);
 139     }
 140 
 141     /**
 142      * Test Thread.currentThread before/after acquiring lock.
 143      */
 144     @Test
 145     void testCurrentThread3() throws Exception {
 146         var ref1 = new AtomicReference<Thread>();
 147         var ref2 = new AtomicReference<Thread>();
 148         var ref3 = new AtomicReference<Thread>();
 149         var lock = new ReentrantLock();
 150         var thread = Thread.ofVirtual().unstarted(() -> {
 151             ref1.set(Thread.currentThread());
 152             lock.lock();
 153             try {
 154                 ref2.set(Thread.currentThread());
 155             } finally {
 156                 lock.unlock();
 157             }
 158             ref3.set(Thread.currentThread());
 159         });
 160         lock.lock();
 161         try {
 162             thread.start();
 163             await(thread, Thread.State.WAITING);
 164         } finally {
 165             lock.unlock();
 166         }
 167         thread.join();
 168         assertTrue(ref1.get() == thread);
 169         assertTrue(ref2.get() == thread);
 170         assertTrue(ref3.get() == thread);
 171     }
 172 
 173     /**
 174      * Test Thread::run.
 175      */
 176     @Test
 177     void testRun1() throws Exception {
 178         var ref = new AtomicBoolean();
 179         var thread = Thread.ofVirtual().unstarted(() -> ref.set(true));
 180         thread.run();
 181         assertFalse(ref.get());
 182     }
 183 
 184     /**
 185      * Test Thread::start.
 186      */
 187     @Test
 188     void testStart1() throws Exception {
 189         var ref = new AtomicBoolean();
 190         var thread = Thread.ofVirtual().unstarted(() -> ref.set(true));
 191         assertFalse(ref.get());
 192         thread.start();
 193         thread.join();
 194         assertTrue(ref.get());
 195     }
 196 
 197     /**
 198      * Test Thread::start, thread already started.
 199      */
 200     @Test
 201     void testStart2() throws Exception {
 202         var thread = Thread.ofVirtual().start(LockSupport::park);
 203         try {
 204             assertThrows(IllegalThreadStateException.class, thread::start);
 205         } finally {
 206             LockSupport.unpark(thread);
 207             thread.join();
 208         }
 209     }
 210 
 211     /**
 212      * Test Thread::start, thread already terminated.
 213      */
 214     @Test
 215     void testStart3() throws Exception {
 216         var thread = Thread.ofVirtual().start(() -> { });
 217         thread.join();
 218         assertThrows(IllegalThreadStateException.class, thread::start);
 219     }
 220 
 221     /**
 222      * Test Thread.startVirtualThread.
 223      */
 224     @Test
 225     void testStartVirtualThread() throws Exception {
 226         var ref = new AtomicReference<Thread>();
 227         Thread vthread = Thread.startVirtualThread(() -> {
 228             ref.set(Thread.currentThread());
 229             LockSupport.park();
 230         });
 231         try {
 232             assertTrue(vthread.isVirtual());
 233 
 234             // Thread.currentThread() returned by the virtual thread
 235             Thread current;
 236             while ((current = ref.get()) == null) {
 237                 Thread.sleep(10);
 238             }
 239             assertTrue(current == vthread);
 240         } finally {
 241             LockSupport.unpark(vthread);
 242         }
 243 
 244         assertThrows(NullPointerException.class, () -> Thread.startVirtualThread(null));
 245     }
 246 
 247     /**
 248      * Test Thread::stop from current thread.
 249      */
 250     @Test
 251     void testStop1() throws Exception {
 252         VThreadRunner.run(() -> {
 253             Thread t = Thread.currentThread();
 254             assertThrows(UnsupportedOperationException.class, t::stop);
 255         });
 256     }
 257 
 258     /**
 259      * Test Thread::stop from another thread.
 260      */
 261     @Test
 262     void testStop2() throws Exception {
 263         var thread = Thread.ofVirtual().start(() -> {
 264             try {
 265                 Thread.sleep(20*1000);
 266             } catch (InterruptedException e) { }
 267         });
 268         try {
 269             assertThrows(UnsupportedOperationException.class, thread::stop);
 270         } finally {
 271             thread.interrupt();
 272             thread.join();
 273         }
 274     }
 275 
 276     /**
 277      * Test Thread::suspend from current thread.
 278      */
 279     @Test
 280     void testSuspend1() throws Exception {
 281         VThreadRunner.run(() -> {
 282             Thread t = Thread.currentThread();
 283             assertThrows(UnsupportedOperationException.class, t::suspend);
 284         });
 285     }
 286 
 287     /**
 288      * Test Thread::suspend from another thread.
 289      */
 290     @Test
 291     void testSuspend2() throws Exception {
 292         var thread = Thread.ofVirtual().start(() -> {
 293             try {
 294                 Thread.sleep(20*1000);
 295             } catch (InterruptedException e) { }
 296         });
 297         try {
 298             assertThrows(UnsupportedOperationException.class, () -> thread.suspend());
 299         } finally {
 300             thread.interrupt();
 301             thread.join();
 302         }
 303     }
 304 
 305     /**
 306      * Test Thread::resume from current thread.
 307      */
 308     @Test
 309     void testResume1() throws Exception {
 310         VThreadRunner.run(() -> {
 311             Thread t = Thread.currentThread();
 312             assertThrows(UnsupportedOperationException.class, t::resume);
 313         });
 314     }
 315 
 316     /**
 317      * Test Thread::resume from another thread.
 318      */
 319     @Test
 320     void testResume2() throws Exception {
 321         var thread = Thread.ofVirtual().start(() -> {
 322             try {
 323                 Thread.sleep(20*1000);
 324             } catch (InterruptedException e) { }
 325         });
 326         try {
 327             assertThrows(UnsupportedOperationException.class, () -> thread.resume());
 328         } finally {
 329             thread.interrupt();
 330             thread.join();
 331         }
 332     }
 333 
 334     /**
 335      * Test Thread.join before thread starts, platform thread invokes join.
 336      */
 337     @Test
 338     void testJoin1() throws Exception {
 339         var thread = Thread.ofVirtual().unstarted(() -> { });
 340 
 341         thread.join();
 342         thread.join(0);
 343         thread.join(0, 0);
 344         thread.join(100);
 345         thread.join(100, 0);
 346         assertThrows(IllegalThreadStateException.class,
 347                 () -> thread.join(Duration.ofMillis(-100)));
 348         assertThrows(IllegalThreadStateException.class,
 349                 () -> thread.join(Duration.ofMillis(0)));
 350         assertThrows(IllegalThreadStateException.class,
 351                 () -> thread.join(Duration.ofMillis(100)));
 352     }
 353 
 354     /**
 355      * Test Thread.join before thread starts, virtual thread invokes join.
 356      */
 357     @Test
 358     void testJoin2() throws Exception {
 359         VThreadRunner.run(this::testJoin1);
 360     }
 361 
 362     /**
 363      * Test Thread.join where thread does not terminate, platform thread invokes join.
 364      */
 365     @Test
 366     void testJoin3() throws Exception {
 367         var thread = Thread.ofVirtual().start(LockSupport::park);
 368         try {
 369             thread.join(20);
 370             thread.join(20, 0);
 371             thread.join(20, 20);
 372             thread.join(0, 20);
 373             assertFalse(thread.join(Duration.ofMillis(-20)));
 374             assertFalse(thread.join(Duration.ofMillis(0)));
 375             assertFalse(thread.join(Duration.ofMillis(20)));
 376             assertTrue(thread.isAlive());
 377         } finally {
 378             LockSupport.unpark(thread);
 379             thread.join();
 380         }
 381     }
 382 
 383     /**
 384      * Test Thread.join where thread does not terminate, virtual thread invokes join.
 385      */
 386     @Test
 387     void testJoin4() throws Exception {
 388         VThreadRunner.run(this::testJoin3);
 389     }
 390 
 391     /**
 392      * Test Thread.join where thread terminates, platform thread invokes join.
 393      */
 394     @Test
 395     void testJoin5() throws Exception {
 396         var thread = Thread.ofVirtual().start(() -> {
 397             try {
 398                 Thread.sleep(50);
 399             } catch (InterruptedException e) { }
 400         });
 401         thread.join();
 402         assertFalse(thread.isAlive());
 403     }
 404 
 405     /**
 406      * Test Thread.join where thread terminates, virtual thread invokes join.
 407      */
 408     @Test
 409     void testJoin6() throws Exception {
 410         VThreadRunner.run(this::testJoin5);
 411     }
 412 
 413     /**
 414      * Test Thread.join where thread terminates, platform thread invokes timed-join.
 415      */
 416     @Test
 417     void testJoin7() throws Exception {
 418         var thread = Thread.ofVirtual().start(() -> {
 419             try {
 420                 Thread.sleep(50);
 421             } catch (InterruptedException e) { }
 422         });
 423         thread.join(10*1000);
 424         assertFalse(thread.isAlive());
 425     }
 426 
 427     /**
 428      * Test Thread.join where thread terminates, virtual thread invokes timed-join.
 429      */
 430     @Test
 431     void testJoin8() throws Exception {
 432         VThreadRunner.run(this::testJoin7);
 433     }
 434 
 435     /**
 436      * Test Thread.join where thread terminates, platform thread invokes timed-join.
 437      */
 438     @Test
 439     void testJoin11() throws Exception {
 440         var thread = Thread.ofVirtual().start(() -> {
 441             try {
 442                 Thread.sleep(50);
 443             } catch (InterruptedException e) { }
 444         });
 445         assertTrue(thread.join(Duration.ofSeconds(10)));
 446         assertFalse(thread.isAlive());
 447     }
 448 
 449     /**
 450      * Test Thread.join where thread terminates, virtual thread invokes timed-join.
 451      */
 452     @Test
 453     void testJoin12() throws Exception {
 454         VThreadRunner.run(this::testJoin11);
 455     }
 456 
 457     /**
 458      * Test Thread.join where thread already terminated, platform thread invokes join.
 459      */
 460     @Test
 461     void testJoin13() throws Exception {
 462         var thread = Thread.ofVirtual().start(() -> { });
 463         while (thread.isAlive()) {
 464             Thread.sleep(10);
 465         }
 466         thread.join();
 467         thread.join(0);
 468         thread.join(0, 0);
 469         thread.join(100);
 470         thread.join(100, 0);
 471         assertTrue(thread.join(Duration.ofMillis(-100)));
 472         assertTrue(thread.join(Duration.ofMillis(0)));
 473         assertTrue(thread.join(Duration.ofMillis(100)));
 474     }
 475 
 476     /**
 477      * Test Thread.join where thread already terminated, virtual thread invokes join.
 478      */
 479     @Test
 480     void testJoin14() throws Exception {
 481         VThreadRunner.run(this::testJoin13);
 482     }
 483 
 484     /**
 485      * Test platform thread invoking Thread.join with interrupt status set.
 486      */
 487     @Test
 488     void testJoin15() throws Exception {
 489         var thread = Thread.ofVirtual().start(LockSupport::park);
 490         Thread.currentThread().interrupt();
 491         try {
 492             assertThrows(InterruptedException.class, thread::join);
 493         } finally {
 494             Thread.interrupted();
 495             LockSupport.unpark(thread);
 496             thread.join();
 497         }
 498     }
 499 
 500     /**
 501      * Test virtual thread invoking Thread.join with interrupt status set.
 502      */
 503     @Test
 504     void testJoin16() throws Exception {
 505         VThreadRunner.run(this::testJoin15);
 506     }
 507 
 508     /**
 509      * Test platform thread invoking timed-Thread.join with interrupt status set.
 510      */
 511     @Test
 512     void testJoin17() throws Exception {
 513         var thread = Thread.ofVirtual().start(LockSupport::park);
 514         Thread.currentThread().interrupt();
 515         try {
 516             assertThrows(InterruptedException.class, () -> thread.join(100));
 517         } finally {
 518             Thread.interrupted();
 519             LockSupport.unpark(thread);
 520             thread.join();
 521         }
 522     }
 523 
 524     /**
 525      * Test virtual thread invoking timed-Thread.join with interrupt status set.
 526      */
 527     @Test
 528     void testJoin18() throws Exception {
 529         VThreadRunner.run(this::testJoin17);
 530     }
 531 
 532     /**
 533      * Test platform thread invoking timed-Thread.join with interrupt status set.
 534      */
 535     @Test
 536     void testJoin19() throws Exception {
 537         var thread = Thread.ofVirtual().start(LockSupport::park);
 538         Thread.currentThread().interrupt();
 539         try {
 540             assertThrows(InterruptedException.class,
 541                          () -> thread.join(Duration.ofMillis(100)));
 542         } finally {
 543             Thread.interrupted();
 544             LockSupport.unpark(thread);
 545             thread.join();
 546         }
 547     }
 548 
 549     /**
 550      * Test virtual thread invoking timed-Thread.join with interrupt status set.
 551      */
 552     @Test
 553     void testJoin20() throws Exception {
 554         VThreadRunner.run(this::testJoin19);
 555     }
 556 
 557     /**
 558      * Test interrupt of platform thread blocked in Thread.join.
 559      */
 560     @Test
 561     void testJoin21() throws Exception {
 562         var thread = Thread.ofVirtual().start(LockSupport::park);
 563         scheduleInterrupt(Thread.currentThread(), 100);
 564         try {
 565             assertThrows(InterruptedException.class, thread::join);
 566         } finally {
 567             Thread.interrupted();
 568             LockSupport.unpark(thread);
 569             thread.join();
 570         }
 571     }
 572 
 573     /**
 574      * Test interrupt of virtual thread blocked in Thread.join.
 575      */
 576     @Test
 577     void testJoin22() throws Exception {
 578         VThreadRunner.run(this::testJoin17);
 579     }
 580 
 581     /**
 582      * Test interrupt of platform thread blocked in timed-Thread.join.
 583      */
 584     @Test
 585     void testJoin23() throws Exception {
 586         var thread = Thread.ofVirtual().start(LockSupport::park);
 587         scheduleInterrupt(Thread.currentThread(), 100);
 588         try {
 589             assertThrows(InterruptedException.class, () -> thread.join(10*1000));
 590         } finally {
 591             Thread.interrupted();
 592             LockSupport.unpark(thread);
 593             thread.join();
 594         }
 595     }
 596 
 597     /**
 598      * Test interrupt of virtual thread blocked in Thread.join.
 599      */
 600     @Test
 601     void testJoin24() throws Exception {
 602         VThreadRunner.run(this::testJoin23);
 603     }
 604 
 605     /**
 606      * Test interrupt of platform thread blocked in Thread.join.
 607      */
 608     @Test
 609     void testJoin25() throws Exception {
 610         var thread = Thread.ofVirtual().start(LockSupport::park);
 611         scheduleInterrupt(Thread.currentThread(), 100);
 612         try {
 613             assertThrows(InterruptedException.class,
 614                          () -> thread.join(Duration.ofSeconds(10)));
 615         } finally {
 616             Thread.interrupted();
 617             LockSupport.unpark(thread);
 618             thread.join();
 619         }
 620     }
 621 
 622     /**
 623      * Test interrupt of virtual thread blocked in Thread.join.
 624      */
 625     @Test
 626     void testJoin26() throws Exception {
 627         VThreadRunner.run(this::testJoin25);
 628     }
 629 
 630     /**
 631      * Test virtual thread calling Thread.join to wait for platform thread to terminate.
 632      */
 633     @Test
 634     void testJoin27() throws Exception {
 635         AtomicBoolean done = new AtomicBoolean();
 636         VThreadRunner.run(() -> {
 637             var thread = new Thread(() -> {
 638                 while (!done.get()) {
 639                     LockSupport.park();
 640                 }
 641             });
 642             thread.start();
 643             try {
 644                 assertFalse(thread.join(Duration.ofMillis(-100)));
 645                 assertFalse(thread.join(Duration.ofMillis(0)));
 646                 assertFalse(thread.join(Duration.ofMillis(100)));
 647             } finally {
 648                 done.set(true);
 649                 LockSupport.unpark(thread);
 650                 thread.join();
 651             }
 652         });
 653     }
 654 
 655     /**
 656      * Test virtual thread calling Thread.join to wait for platform thread to terminate.
 657      */
 658     @Test
 659     void testJoin28() throws Exception {
 660         long nanos = TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS);
 661         VThreadRunner.run(() -> {
 662             var thread = new Thread(() -> LockSupport.parkNanos(nanos));
 663             thread.start();
 664             try {
 665                 assertTrue(thread.join(Duration.ofSeconds(Integer.MAX_VALUE)));
 666                 assertFalse(thread.isAlive());
 667             } finally {
 668                 LockSupport.unpark(thread);
 669                 thread.join();
 670             }
 671         });
 672     }
 673 
 674     /**
 675      * Test virtual thread with interrupt status set calling Thread.join to wait
 676      * for platform thread to terminate.
 677      */
 678     @Test
 679     void testJoin29() throws Exception {
 680         VThreadRunner.run(() -> {
 681             var thread = new Thread(LockSupport::park);
 682             thread.start();
 683             Thread.currentThread().interrupt();
 684             try {
 685                 thread.join(Duration.ofSeconds(Integer.MAX_VALUE));
 686                 fail("join not interrupted");
 687             } catch (InterruptedException expected) {
 688                 assertFalse(Thread.interrupted());
 689             } finally {
 690                 LockSupport.unpark(thread);
 691                 thread.join();
 692             }
 693         });
 694     }
 695 
 696     /**
 697      * Test interrupting virtual thread that is waiting in Thread.join for
 698      * platform thread to terminate.
 699      */
 700     @Test
 701     void testJoin30() throws Exception {
 702         VThreadRunner.run(() -> {
 703             AtomicBoolean done = new AtomicBoolean();
 704             var thread = new Thread(() -> {
 705                 while (!done.get()) {
 706                     LockSupport.park();
 707                 }
 708             });
 709             thread.start();
 710             scheduleInterrupt(Thread.currentThread(), 100);
 711             try {
 712                 thread.join(Duration.ofSeconds(Integer.MAX_VALUE));
 713                 fail("join not interrupted");
 714             } catch (InterruptedException expected) {
 715                 assertFalse(Thread.interrupted());
 716             } finally {
 717                 done.set(true);
 718                 LockSupport.unpark(thread);
 719                 thread.join();
 720             }
 721         });
 722     }
 723 
 724     /**
 725      * Test platform thread invoking Thread.join on a thread that is parking
 726      * and unparking.
 727      */
 728     @Test
 729     void testJoin31() throws Exception {
 730         Thread thread = Thread.ofVirtual().start(() -> {
 731             synchronized (lock) {
 732                 for (int i = 0; i < 10; i++) {
 733                     LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
 734                 }
 735             }
 736         });
 737         thread.join();
 738         assertFalse(thread.isAlive());
 739     }
 740 
 741     /**
 742      * Test virtual thread invoking Thread.join on a thread that is parking
 743      * and unparking.
 744      */
 745     @Test
 746     void testJoin32() throws Exception {
 747         VThreadRunner.run(this::testJoin31);
 748     }
 749 
 750     /**
 751      * Test platform thread invoking timed-Thread.join on a thread that is parking
 752      * and unparking while pinned.
 753      */
 754     @Test
 755     void testJoin33() throws Exception {
 756         AtomicBoolean done = new AtomicBoolean();
 757         Thread thread = Thread.ofVirtual().start(() -> {
 758             synchronized (lock) {
 759                 while (!done.get()) {
 760                     LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
 761                 }
 762             }
 763         });
 764         try {
 765             assertFalse(thread.join(Duration.ofMillis(100)));
 766         } finally {
 767             done.set(true);
 768             thread.join();
 769         }
 770     }
 771 
 772     /**
 773      * Test virtual thread invoking timed-Thread.join on a thread that is parking
 774      * and unparking while pinned.
 775      */
 776     @Test
 777     void testJoin34() throws Exception {
 778         // need at least two carrier threads due to pinning
 779         int previousParallelism = VThreadRunner.ensureParallelism(2);
 780         try {
 781             VThreadRunner.run(this::testJoin33);
 782         } finally {
 783             // restore
 784             VThreadRunner.setParallelism(previousParallelism);
 785         }
 786     }
 787 
 788     /**
 789      * Test Thread.join(null).
 790      */
 791     @Test
 792     void testJoin35() throws Exception {
 793         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
 794 
 795         // unstarted
 796         assertThrows(NullPointerException.class, () -> thread.join(null));
 797 
 798         // started
 799         thread.start();
 800         try {
 801             assertThrows(NullPointerException.class, () -> thread.join(null));
 802         } finally {
 803             LockSupport.unpark(thread);
 804         }
 805         thread.join();
 806 
 807         // terminated
 808         assertThrows(NullPointerException.class, () -> thread.join(null));
 809     }
 810 
 811     /**
 812      * Test Thread.interrupt on current thread.
 813      */
 814     @Test
 815     void testInterrupt1() throws Exception {
 816         VThreadRunner.run(() -> {
 817             Thread me = Thread.currentThread();
 818             assertFalse(me.isInterrupted());
 819             me.interrupt();
 820             assertTrue(me.isInterrupted());
 821             Thread.interrupted();  // clear interrupt status
 822             assertFalse(me.isInterrupted());
 823             me.interrupt();
 824         });
 825     }
 826 
 827     /**
 828      * Test Thread.interrupt before thread started.
 829      */
 830     @Test
 831     void testInterrupt2() throws Exception {
 832         var thread = Thread.ofVirtual().unstarted(() -> { });
 833         thread.interrupt();
 834         assertTrue(thread.isInterrupted());
 835     }
 836 
 837     /**
 838      * Test Thread.interrupt after thread started.
 839      */
 840     @Test
 841     void testInterrupt3() throws Exception {
 842         var thread = Thread.ofVirtual().start(() -> { });
 843         thread.join();
 844         thread.interrupt();
 845         assertTrue(thread.isInterrupted());
 846     }
 847 
 848     /**
 849      * Test termination with interrupt status set.
 850      */
 851     @Test
 852     void testInterrupt4() throws Exception {
 853         var thread = Thread.ofVirtual().start(() -> {
 854             Thread.currentThread().interrupt();
 855         });
 856         thread.join();
 857         assertTrue(thread.isInterrupted());
 858     }
 859 
 860     /**
 861      * Test Thread.interrupt of thread blocked in Selector.select.
 862      */
 863     @Test
 864     void testInterrupt5() throws Exception {
 865         var exception = new AtomicReference<Exception>();
 866         var thread = Thread.ofVirtual().start(() -> {
 867             try {
 868                 try (var sel = Selector.open()) {
 869                     sel.select();
 870                     assertTrue(Thread.currentThread().isInterrupted());
 871                 }
 872             } catch (Exception e) {
 873                 exception.set(e);
 874             }
 875         });
 876         Thread.sleep(100);  // give time for thread to block
 877         thread.interrupt();
 878         thread.join();
 879         assertNull(exception.get());
 880     }
 881 
 882     /**
 883      * Test Thread.interrupt of thread parked in sleep.
 884      */
 885     @Test
 886     void testInterrupt6() throws Exception {
 887         var exception = new AtomicReference<Exception>();
 888         var thread = Thread.ofVirtual().start(() -> {
 889             try {
 890                 try {
 891                     Thread.sleep(60*1000);
 892                     fail("sleep not interrupted");
 893                 } catch (InterruptedException e) {
 894                     // interrupt status should be reset
 895                     assertFalse(Thread.interrupted());
 896                 }
 897             } catch (Exception e) {
 898                 exception.set(e);
 899             }
 900         });
 901         await(thread, Thread.State.TIMED_WAITING);
 902         thread.interrupt();
 903         thread.join();
 904         assertNull(exception.get());
 905     }
 906 
 907     /**
 908      * Test Thread.interrupt of parked thread.
 909      */
 910     @Test
 911     void testInterrupt7() throws Exception {
 912         var exception = new AtomicReference<Exception>();
 913         var thread = Thread.ofVirtual().start(() -> {
 914             try {
 915                 LockSupport.park();
 916                 assertTrue(Thread.currentThread().isInterrupted());
 917             } catch (Exception e) {
 918                 exception.set(e);
 919             }
 920         });
 921         await(thread, Thread.State.WAITING);
 922         thread.interrupt();
 923         thread.join();
 924         assertNull(exception.get());
 925     }
 926 
 927     /**
 928      * Test trying to park with interrupt status set.
 929      */
 930     @Test
 931     void testInterrupt8() throws Exception {
 932         VThreadRunner.run(() -> {
 933             Thread me = Thread.currentThread();
 934             me.interrupt();
 935             LockSupport.park();
 936             assertTrue(Thread.interrupted());
 937         });
 938     }
 939 
 940     /**
 941      * Test trying to wait with interrupt status set.
 942      */
 943     @Test
 944     void testInterrupt9() throws Exception {
 945         VThreadRunner.run(() -> {
 946             Thread me = Thread.currentThread();
 947             me.interrupt();
 948             synchronized (lock) {
 949                 try {
 950                     lock.wait();
 951                     fail("wait not interrupted");
 952                 } catch (InterruptedException expected) {
 953                     assertFalse(Thread.interrupted());
 954                 }
 955             }
 956         });
 957     }
 958 
 959     /**
 960      * Test trying to block with interrupt status set.
 961      */
 962     @Test
 963     void testInterrupt10() throws Exception {
 964         VThreadRunner.run(() -> {
 965             Thread me = Thread.currentThread();
 966             me.interrupt();
 967             try (Selector sel = Selector.open()) {
 968                 sel.select();
 969                 assertTrue(Thread.interrupted());
 970             }
 971         });
 972     }
 973 
 974     /**
 975      * Test Thread.getName and setName from current thread, started without name.
 976      */
 977     @Test
 978     void testSetName1() throws Exception {
 979         VThreadRunner.run(() -> {
 980             Thread me = Thread.currentThread();
 981             assertTrue(me.getName().isEmpty());
 982             me.setName("fred");
 983             assertEquals("fred", me.getName());
 984         });
 985     }
 986 
 987     /**
 988      * Test Thread.getName and setName from current thread, started with name.
 989      */
 990     @Test
 991     void testSetName2() throws Exception {
 992         VThreadRunner.run("fred", () -> {
 993             Thread me = Thread.currentThread();
 994             assertEquals("fred", me.getName());
 995             me.setName("joe");
 996             assertEquals("joe", me.getName());
 997         });
 998     }
 999 
1000     /**
1001      * Test Thread.getName and setName from another thread.
1002      */
1003     @Test
1004     void testSetName3() throws Exception {
1005         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1006         assertTrue(thread.getName().isEmpty());
1007 
1008         // not started
1009         thread.setName("fred1");
1010         assertEquals("fred1", thread.getName());
1011 
1012         // started
1013         thread.start();
1014         try {
1015             assertEquals("fred1", thread.getName());
1016             thread.setName("fred2");
1017             assertEquals("fred2", thread.getName());
1018         } finally {
1019             LockSupport.unpark(thread);
1020             thread.join();
1021         }
1022 
1023         // terminated
1024         assertEquals("fred2", thread.getName());
1025         thread.setName("fred3");
1026         assertEquals("fred3", thread.getName());
1027     }
1028 
1029     /**
1030      * Test Thread.getPriority and setPriority from current thread.
1031      */
1032     @Test
1033     void testSetPriority1() throws Exception {
1034         VThreadRunner.run(() -> {
1035             Thread me = Thread.currentThread();
1036             assertEquals(Thread.NORM_PRIORITY, me.getPriority());
1037 
1038             me.setPriority(Thread.MAX_PRIORITY);
1039             assertEquals(Thread.NORM_PRIORITY, me.getPriority());
1040 
1041             me.setPriority(Thread.NORM_PRIORITY);
1042             assertEquals(Thread.NORM_PRIORITY, me.getPriority());
1043 
1044             me.setPriority(Thread.MIN_PRIORITY);
1045             assertEquals(Thread.NORM_PRIORITY, me.getPriority());
1046 
1047             assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
1048         });
1049     }
1050 
1051     /**
1052      * Test Thread.getPriority and setPriority from another thread.
1053      */
1054     @Test
1055     void testSetPriority2() throws Exception {
1056         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1057 
1058         // not started
1059         assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1060 
1061         thread.setPriority(Thread.MAX_PRIORITY);
1062         assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1063 
1064         thread.setPriority(Thread.NORM_PRIORITY);
1065         assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1066 
1067         thread.setPriority(Thread.MIN_PRIORITY);
1068         assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1069 
1070         assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
1071 
1072         // running
1073         thread.start();
1074         try {
1075             assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1076             thread.setPriority(Thread.NORM_PRIORITY);
1077 
1078             thread.setPriority(Thread.MAX_PRIORITY);
1079             assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1080 
1081             thread.setPriority(Thread.NORM_PRIORITY);
1082             assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1083 
1084             thread.setPriority(Thread.MIN_PRIORITY);
1085             assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1086 
1087             assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
1088 
1089         } finally {
1090             LockSupport.unpark(thread);
1091         }
1092         thread.join();
1093 
1094         // terminated
1095         assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
1096     }
1097 
1098     /**
1099      * Test Thread.isDaemon and setDaemon from current thread.
1100      */
1101     @Test
1102     void testSetDaemon1() throws Exception {
1103         VThreadRunner.run(() -> {
1104             Thread me = Thread.currentThread();
1105             assertTrue(me.isDaemon());
1106             assertThrows(IllegalThreadStateException.class, () -> me.setDaemon(true));
1107             assertThrows(IllegalArgumentException.class, () -> me.setDaemon(false));
1108         });
1109     }
1110 
1111     /**
1112      * Test Thread.isDaemon and setDaemon from another thread.
1113      */
1114     @Test
1115     void testSetDaemon2() throws Exception {
1116         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1117 
1118         // not started
1119         assertTrue(thread.isDaemon());
1120         thread.setDaemon(true);
1121         assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
1122 
1123         // running
1124         thread.start();
1125         try {
1126             assertTrue(thread.isDaemon());
1127             assertThrows(IllegalThreadStateException.class, () -> thread.setDaemon(true));
1128             assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
1129         } finally {
1130             LockSupport.unpark(thread);
1131         }
1132         thread.join();
1133 
1134         // terminated
1135         assertTrue(thread.isDaemon());
1136     }
1137 
1138     /**
1139      * Test Thread.yield releases thread when not pinned.
1140      */
1141     @Test
1142     void testYield1() throws Exception {
1143         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1144         var list = new CopyOnWriteArrayList<String>();
1145         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1146             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1147             ThreadFactory factory = builder.factory();
1148             var thread = factory.newThread(() -> {
1149                 list.add("A");
1150                 var child = factory.newThread(() -> {
1151                     list.add("B");
1152                     Thread.yield();
1153                     list.add("B");
1154                 });
1155                 child.start();
1156                 Thread.yield();
1157                 list.add("A");
1158                 try { child.join(); } catch (InterruptedException e) { }
1159             });
1160             thread.start();
1161             thread.join();
1162         }
1163         assertEquals(List.of("A", "B", "A", "B"), list);
1164     }
1165 
1166     /**
1167      * Test Thread.yield when thread is pinned.
1168      */
1169     @Test
1170     void testYield2() throws Exception {
1171         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1172         var list = new CopyOnWriteArrayList<String>();
1173         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1174             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1175             ThreadFactory factory = builder.factory();
1176             var thread = factory.newThread(() -> {
1177                 list.add("A");
1178                 var child = factory.newThread(() -> {
1179                     list.add("B");
1180                 });
1181                 child.start();
1182                 synchronized (lock) {
1183                     Thread.yield();   // pinned so will be a no-op
1184                     list.add("A");
1185                 }
1186                 try { child.join(); } catch (InterruptedException e) { }
1187             });
1188             thread.start();
1189             thread.join();
1190         }
1191         assertEquals(List.of("A", "A", "B"), list);
1192     }
1193 
1194     /**
1195      * Test that Thread.yield does not consume the thread's parking permit.
1196      */
1197     @Test
1198     void testYield3() throws Exception {
1199         var thread = Thread.ofVirtual().start(() -> {
1200             LockSupport.unpark(Thread.currentThread());
1201             Thread.yield();
1202             LockSupport.park();  // should not park
1203         });
1204         thread.join();
1205     }
1206 
1207     /**
1208      * Test that Thread.yield does not make available the thread's parking permit.
1209      */
1210     @Test
1211     void testYield4() throws Exception {
1212         var thread = Thread.ofVirtual().start(() -> {
1213             Thread.yield();
1214             LockSupport.park();  // should park
1215         });
1216         try {
1217             await(thread, Thread.State.WAITING);
1218         } finally {
1219             LockSupport.unpark(thread);
1220             thread.join();
1221         }
1222     }
1223 
1224     /**
1225      * Test Thread.onSpinWait.
1226      */
1227     @Test
1228     void testOnSpinWait() throws Exception {
1229         VThreadRunner.run(() -> {
1230             Thread me = Thread.currentThread();
1231             Thread.onSpinWait();
1232             assertTrue(Thread.currentThread() == me);
1233         });
1234     }
1235 
1236     /**
1237      * Test Thread.sleep(-1).
1238      */
1239     @Test
1240     void testSleep1() throws Exception {
1241         VThreadRunner.run(() -> {
1242             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1));
1243             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1, 0));
1244             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, -1));
1245             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, 1_000_000));
1246         });
1247         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(-1)));
1248     }
1249 
1250     /**
1251      * Test Thread.sleep(0).
1252      */
1253     @Test
1254     void testSleep2() throws Exception {
1255         VThreadRunner.run(() -> Thread.sleep(0));
1256         VThreadRunner.run(() -> Thread.sleep(0, 0));
1257         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(0)));
1258     }
1259 
1260     /**
1261      * Tasks that sleep for 1 second.
1262      */
1263     static Stream<ThrowingRunnable> oneSecondSleepers() {
1264         return Stream.of(
1265                 () -> Thread.sleep(1000),
1266                 () -> Thread.sleep(Duration.ofSeconds(1))
1267         );
1268     }
1269 
1270     /**
1271      * Test Thread.sleep duration.
1272      */
1273     @ParameterizedTest
1274     @MethodSource("oneSecondSleepers")
1275     void testSleep3(ThrowingRunnable sleeper) throws Exception {
1276         VThreadRunner.run(() -> {
1277             long start = millisTime();
1278             sleeper.run();
1279             expectDuration(start, /*min*/900, /*max*/20_000);
1280         });
1281     }
1282 
1283     /**
1284      * Tasks that sleep for zero or longer duration.
1285      */
1286     static Stream<ThrowingRunnable> sleepers() {
1287         return Stream.of(
1288                 () -> Thread.sleep(0),
1289                 () -> Thread.sleep(0, 0),
1290                 () -> Thread.sleep(1000),
1291                 () -> Thread.sleep(1000, 0),
1292                 () -> Thread.sleep(Duration.ofMillis(0)),
1293                 () -> Thread.sleep(Duration.ofMillis(1000))
1294         );
1295     }
1296 
1297     /**
1298      * Test Thread.sleep with interrupt status set.
1299      */
1300     @ParameterizedTest
1301     @MethodSource("sleepers")
1302     void testSleep4(ThrowingRunnable sleeper) throws Exception {
1303         VThreadRunner.run(() -> {
1304             Thread me = Thread.currentThread();
1305             me.interrupt();
1306             try {
1307                 sleeper.run();
1308                 fail("sleep was not interrupted");
1309             } catch (InterruptedException e) {
1310                 // expected
1311                 assertFalse(me.isInterrupted());
1312             }
1313         });
1314     }
1315 
1316     /**
1317      * Test Thread.sleep with interrupt status set and a negative duration.
1318      */
1319     @Test
1320     void testSleep4() throws Exception {
1321         VThreadRunner.run(() -> {
1322             Thread me = Thread.currentThread();
1323             me.interrupt();
1324             Thread.sleep(Duration.ofMillis(-1000));  // does nothing
1325             assertTrue(me.isInterrupted());
1326         });
1327     }
1328 
1329     /**
1330      * Tasks that sleep for a long time.
1331      */
1332     static Stream<ThrowingRunnable> longSleepers() {
1333         return Stream.of(
1334                 () -> Thread.sleep(20_000),
1335                 () -> Thread.sleep(20_000, 0),
1336                 () -> Thread.sleep(Duration.ofSeconds(20))
1337         );
1338     }
1339 
1340     /**
1341      * Test interrupting Thread.sleep.
1342      */
1343     @ParameterizedTest
1344     @MethodSource("longSleepers")
1345     void testSleep5(ThrowingRunnable sleeper) throws Exception {
1346         VThreadRunner.run(() -> {
1347             Thread t = Thread.currentThread();
1348             scheduleInterrupt(t, 100);
1349             try {
1350                 sleeper.run();
1351                 fail("sleep was not interrupted");
1352             } catch (InterruptedException e) {
1353                 // interrupt status should be cleared
1354                 assertFalse(t.isInterrupted());
1355             }
1356         });
1357     }
1358 
1359     /**
1360      * Test that Thread.sleep does not disrupt parking permit.
1361      */
1362     @Test
1363     void testSleep6() throws Exception {
1364         VThreadRunner.run(() -> {
1365             LockSupport.unpark(Thread.currentThread());
1366 
1367             long start = millisTime();
1368             Thread.sleep(1000);
1369             expectDuration(start, /*min*/900, /*max*/20_000);
1370 
1371             // check that parking permit was not consumed
1372             LockSupport.park();
1373         });
1374     }
1375 
1376     /**
1377      * Test that Thread.sleep is not disrupted by unparking thread.
1378      */
1379     @Test
1380     void testSleep7() throws Exception {
1381         AtomicReference<Exception> exc = new AtomicReference<>();
1382         var thread = Thread.ofVirtual().start(() -> {
1383             try {
1384                 long start = millisTime();
1385                 Thread.sleep(1000);
1386                 expectDuration(start, /*min*/900, /*max*/20_000);
1387             } catch (Exception e) {
1388                 exc.set(e);
1389             }
1390 
1391         });
1392         // attempt to disrupt sleep
1393         for (int i = 0; i < 5; i++) {
1394             Thread.sleep(20);
1395             LockSupport.unpark(thread);
1396         }
1397         thread.join();
1398         Exception e = exc.get();
1399         if (e != null) {
1400             throw e;
1401         }
1402     }
1403 
1404     /**
1405      * Test Thread.sleep when pinned.
1406      */
1407     @Test
1408     void testSleep8() throws Exception {
1409         VThreadRunner.run(() -> {
1410             long start = millisTime();
1411             synchronized (lock) {
1412                 Thread.sleep(1000);
1413             }
1414             expectDuration(start, /*min*/900, /*max*/20_000);
1415         });
1416     }
1417 
1418     /**
1419      * Test Thread.sleep when pinned and with interrupt status set.
1420      */
1421     @Test
1422     void testSleep9() throws Exception {
1423         VThreadRunner.run(() -> {
1424             Thread me = Thread.currentThread();
1425             me.interrupt();
1426             try {
1427                 synchronized (lock) {
1428                     Thread.sleep(2000);
1429                 }
1430                 fail("sleep not interrupted");
1431             } catch (InterruptedException e) {
1432                 // expected
1433                 assertFalse(me.isInterrupted());
1434             }
1435         });
1436     }
1437 
1438     /**
1439      * Test interrupting Thread.sleep when pinned.
1440      */
1441     @Test
1442     void testSleep10() throws Exception {
1443         VThreadRunner.run(() -> {
1444             Thread t = Thread.currentThread();
1445             scheduleInterrupt(t, 100);
1446             try {
1447                 synchronized (lock) {
1448                     Thread.sleep(20 * 1000);
1449                 }
1450                 fail("sleep not interrupted");
1451             } catch (InterruptedException e) {
1452                 // interrupt status should be cleared
1453                 assertFalse(t.isInterrupted());
1454             }
1455         });
1456     }
1457 
1458     /**
1459      * Test Thread.sleep(null).
1460      */
1461     @Test
1462     void testSleep11() throws Exception {
1463         assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1464         VThreadRunner.run(() -> {
1465             assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1466         });
1467     }
1468 
1469     /**
1470      * Returns the current time in milliseconds.
1471      */
1472     private static long millisTime() {
1473         long now = System.nanoTime();
1474         return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
1475     }
1476 
1477     /**
1478      * Check the duration of a task
1479      * @param start start time, in milliseconds
1480      * @param min minimum expected duration, in milliseconds
1481      * @param max maximum expected duration, in milliseconds
1482      * @return the duration (now - start), in milliseconds
1483      */
1484     private static void expectDuration(long start, long min, long max) {
1485         long duration = millisTime() - start;
1486         assertTrue(duration >= min,
1487                 "Duration " + duration + "ms, expected >= " + min + "ms");
1488         assertTrue(duration <= max,
1489                 "Duration " + duration + "ms, expected <= " + max + "ms");
1490     }
1491 
1492     /**
1493      * Test Thread.xxxContextClassLoader from the current thread.
1494      */
1495     @Test
1496     void testContextClassLoader1() throws Exception {
1497         ClassLoader loader = new ClassLoader() { };
1498         VThreadRunner.run(() -> {
1499             Thread t = Thread.currentThread();
1500             t.setContextClassLoader(loader);
1501             assertTrue(t.getContextClassLoader() == loader);
1502         });
1503     }
1504 
1505     /**
1506      * Test inheriting initial value of TCCL from platform thread.
1507      */
1508     @Test
1509     void testContextClassLoader2() throws Exception {
1510         ClassLoader loader = new ClassLoader() { };
1511         Thread t = Thread.currentThread();
1512         ClassLoader savedLoader = t.getContextClassLoader();
1513         t.setContextClassLoader(loader);
1514         try {
1515             VThreadRunner.run(() -> {
1516                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1517             });
1518         } finally {
1519             t.setContextClassLoader(savedLoader);
1520         }
1521     }
1522 
1523     /**
1524      * Test inheriting initial value of TCCL from virtual thread.
1525      */
1526     @Test
1527     void testContextClassLoader3() throws Exception {
1528         VThreadRunner.run(() -> {
1529             ClassLoader loader = new ClassLoader() { };
1530             Thread.currentThread().setContextClassLoader(loader);
1531             VThreadRunner.run(() -> {
1532                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1533             });
1534         });
1535     }
1536 
1537     /**
1538      * Test inheriting initial value of TCCL through an intermediate virtual thread.
1539      */
1540     @Test
1541     void testContextClassLoader4() throws Exception {
1542         ClassLoader loader = new ClassLoader() { };
1543         Thread t = Thread.currentThread();
1544         ClassLoader savedLoader = t.getContextClassLoader();
1545         t.setContextClassLoader(loader);
1546         try {
1547             VThreadRunner.run(() -> {
1548                 VThreadRunner.run(() -> {
1549                     assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1550                 });
1551             });
1552         } finally {
1553             t.setContextClassLoader(savedLoader);
1554         }
1555     }
1556 
1557     /**
1558      * Test Thread.xxxContextClassLoader when thread does not inherit the
1559      * initial value of inheritable thread locals.
1560      */
1561     @Test
1562     void testContextClassLoader5() throws Exception {
1563         VThreadRunner.run(() -> {
1564             ClassLoader loader = new ClassLoader() { };
1565             Thread.currentThread().setContextClassLoader(loader);
1566             int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
1567             VThreadRunner.run(characteristics, () -> {
1568                 Thread t = Thread.currentThread();
1569                 assertTrue(t.getContextClassLoader() == ClassLoader.getSystemClassLoader());
1570                 t.setContextClassLoader(loader);
1571                 assertTrue(t.getContextClassLoader() == loader);
1572             });
1573         });
1574     }
1575 
1576     /**
1577      * Test Thread.setUncaughtExceptionHandler.
1578      */
1579     @Test
1580     void testUncaughtExceptionHandler1() throws Exception {
1581         class FooException extends RuntimeException { }
1582         var exception = new AtomicReference<Throwable>();
1583         Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
1584         Thread thread = Thread.ofVirtual().start(() -> {
1585             Thread me = Thread.currentThread();
1586             assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
1587             me.setUncaughtExceptionHandler(handler);
1588             assertTrue(me.getUncaughtExceptionHandler() == handler);
1589             throw new FooException();
1590         });
1591         thread.join();
1592         assertTrue(exception.get() instanceof FooException);
1593         assertNull(thread.getUncaughtExceptionHandler());
1594     }
1595 
1596     /**
1597      * Test default UncaughtExceptionHandler.
1598      */
1599     @Test
1600     void testUncaughtExceptionHandler2() throws Exception {
1601         class FooException extends RuntimeException { }
1602         var exception = new AtomicReference<Throwable>();
1603         Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
1604         Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
1605         Thread.setDefaultUncaughtExceptionHandler(handler);
1606         Thread thread;
1607         try {
1608             thread = Thread.ofVirtual().start(() -> {
1609                 Thread me = Thread.currentThread();
1610                 throw new FooException();
1611             });
1612             thread.join();
1613         } finally {
1614             Thread.setDefaultUncaughtExceptionHandler(savedHandler);
1615         }
1616         assertTrue(exception.get() instanceof FooException);
1617         assertNull(thread.getUncaughtExceptionHandler());
1618     }
1619 
1620     /**
1621      * Test no UncaughtExceptionHandler set.
1622      */
1623     @Test
1624     void testUncaughtExceptionHandler3() throws Exception {
1625         class FooException extends RuntimeException { }
1626         Thread thread = Thread.ofVirtual().start(() -> {
1627             throw new FooException();
1628         });
1629         thread.join();
1630         assertNull(thread.getUncaughtExceptionHandler());
1631     }
1632 
1633     /**
1634      * Test Thread::threadId and getId.
1635      */
1636     @Test
1637     void testThreadId1() throws Exception {
1638         record ThreadIds(long threadId, long id) { }
1639         var ref = new AtomicReference<ThreadIds>();
1640 
1641         Thread vthread = Thread.ofVirtual().unstarted(() -> {
1642             Thread thread = Thread.currentThread();
1643             ref.set(new ThreadIds(thread.threadId(), thread.getId()));
1644             LockSupport.park();
1645         });
1646 
1647         // unstarted
1648         long tid = vthread.threadId();
1649 
1650         // running
1651         ThreadIds tids;
1652         vthread.start();
1653         try {
1654             while ((tids = ref.get()) == null) {
1655                 Thread.sleep(10);
1656             }
1657             assertTrue(tids.threadId() == tid);
1658             assertTrue(tids.id() == tid);
1659         } finally {
1660             LockSupport.unpark(vthread);
1661             vthread.join();
1662         }
1663 
1664         // terminated
1665         assertTrue(vthread.threadId() == tid);
1666         assertTrue(vthread.getId() == tid);
1667     }
1668 
1669     /**
1670      * Test that each Thread has a unique ID
1671      */
1672     @Test
1673     void testThreadId2() throws Exception {
1674         // thread ID should be unique
1675         long tid1 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1676         long tid2 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1677         long tid3 = Thread.currentThread().threadId();
1678         assertFalse(tid1 == tid2);
1679         assertFalse(tid1 == tid3);
1680         assertFalse(tid2 == tid3);
1681     }
1682 
1683     /**
1684      * Test Thread::getState when thread is new/unstarted.
1685      */
1686     @Test
1687     void testGetState1() {
1688         var thread = Thread.ofVirtual().unstarted(() -> { });
1689         assertEquals(Thread.State.NEW, thread.getState());
1690     }
1691 
1692     /**
1693      * Test Thread::getState when thread is terminated.
1694      */
1695     @Test
1696     void testGetState2() throws Exception {
1697         var thread = Thread.ofVirtual().start(() -> { });
1698         thread.join();
1699         assertEquals(Thread.State.TERMINATED, thread.getState());
1700     }
1701 
1702     /**
1703      * Test Thread::getState when thread is runnable (mounted).
1704      */
1705     @Test
1706     void testGetState3() throws Exception {
1707         var started = new CountDownLatch(1);
1708         var done = new AtomicBoolean();
1709         var thread = Thread.ofVirtual().start(() -> {
1710             started.countDown();
1711 
1712             // spin until done
1713             while (!done.get()) {
1714                 Thread.onSpinWait();
1715             }
1716         });
1717         try {
1718             // wait for thread to start
1719             started.await();
1720 
1721             // thread should be runnable
1722             assertEquals(Thread.State.RUNNABLE, thread.getState());
1723         } finally {
1724             done.set(true);
1725             thread.join();
1726         }
1727     }
1728 
1729     /**
1730      * Test Thread::getState when thread is runnable (not mounted).
1731      */
1732     @Test
1733     void testGetState4() throws Exception {
1734         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1735         AtomicBoolean completed = new AtomicBoolean();
1736         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1737             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1738             Thread t1 = builder.start(() -> {
1739                 Thread t2 = builder.unstarted(LockSupport::park);
1740                 assertEquals(Thread.State.NEW, t2.getState());
1741 
1742                 // start t2 to make it runnable
1743                 t2.start();
1744                 try {
1745                     assertEquals(Thread.State.RUNNABLE, t2.getState());
1746 
1747                     // yield to allow t2 to run and park
1748                     Thread.yield();
1749                     assertEquals(Thread.State.WAITING, t2.getState());
1750                 } finally {
1751                     // unpark t2 to make it runnable again
1752                     LockSupport.unpark(t2);
1753                 }
1754 
1755                 // t2 should be runnable (not mounted)
1756                 assertEquals(Thread.State.RUNNABLE, t2.getState());
1757 
1758                 completed.set(true);
1759             });
1760             t1.join();
1761         }
1762         assertTrue(completed.get() == true);
1763     }
1764 
1765     /**
1766      * Test Thread::getState when thread is waiting to enter a monitor.
1767      */
1768     @Test
1769     void testGetState5() throws Exception {
1770         var started = new CountDownLatch(1);
1771         var thread = Thread.ofVirtual().unstarted(() -> {
1772             started.countDown();
1773             synchronized (lock) { }
1774         });
1775         synchronized (lock) {
1776             thread.start();
1777             started.await();
1778 
1779             // wait for thread to block
1780             await(thread, Thread.State.BLOCKED);
1781         }
1782         thread.join();
1783     }
1784 
1785     /**
1786      * Test Thread::getState when thread is waiting in Object.wait.
1787      */
1788     @Test
1789     void testGetState6() throws Exception {
1790         var thread = Thread.ofVirtual().start(() -> {
1791             synchronized (lock) {
1792                 try { lock.wait(); } catch (InterruptedException e) { }
1793             }
1794         });
1795         try {
1796             // wait for thread to wait
1797             await(thread, Thread.State.WAITING);
1798         } finally {
1799             thread.interrupt();
1800             thread.join();
1801         }
1802     }
1803 
1804     /**
1805      * Test Thread::getState when thread is waiting in Object.wait(millis).
1806      */
1807     @Test
1808     void testGetState7() throws Exception {
1809         var thread = Thread.ofVirtual().start(() -> {
1810             synchronized (lock) {
1811                 try {
1812                     lock.wait(Long.MAX_VALUE);
1813                 } catch (InterruptedException e) { }
1814             }
1815         });
1816         try {
1817             // wait for thread to wait
1818             await(thread, Thread.State.TIMED_WAITING);
1819         } finally {
1820             thread.interrupt();
1821             thread.join();
1822         }
1823     }
1824 
1825     /**
1826      * Test Thread::getState when thread is parked.
1827      */
1828     @Test
1829     void testGetState8() throws Exception {
1830         var thread = Thread.ofVirtual().start(LockSupport::park);
1831         try {
1832             await(thread, Thread.State.WAITING);
1833         } finally {
1834             LockSupport.unpark(thread);
1835             thread.join();
1836         }
1837     }
1838 
1839     /**
1840      * Test Thread::getState when thread is timed parked.
1841      */
1842     @Test
1843     void testGetState9() throws Exception {
1844         var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE));
1845         try {
1846             await(thread, Thread.State.TIMED_WAITING);
1847         } finally {
1848             LockSupport.unpark(thread);
1849             thread.join();
1850         }
1851     }
1852 
1853     /**
1854      * Test Thread::getState when thread is parked while holding a monitor.
1855      */
1856     @Test
1857     void testGetState10() throws Exception {
1858         var started = new CountDownLatch(1);
1859         var done = new AtomicBoolean();
1860         var thread = Thread.ofVirtual().start(() -> {
1861             started.countDown();
1862             synchronized (lock) {
1863                 while (!done.get()) {
1864                     LockSupport.park();
1865                 }
1866             }
1867         });
1868         try {
1869             // wait for thread to start
1870             started.await();
1871 
1872             // wait for thread to park
1873             await(thread, Thread.State.WAITING);
1874         } finally {
1875             done.set(true);
1876             LockSupport.unpark(thread);
1877             thread.join();
1878         }
1879     }
1880 
1881     /**
1882      * Test Thread::getState when thread is timed parked while holding a monitor.
1883      */
1884     @Test
1885     void testGetState11() throws Exception {
1886         var started = new CountDownLatch(1);
1887         var done = new AtomicBoolean();
1888         var thread = Thread.ofVirtual().start(() -> {
1889             started.countDown();
1890             synchronized (lock) {
1891                 while (!done.get()) {
1892                     LockSupport.parkNanos(Long.MAX_VALUE);
1893                 }
1894             }
1895         });
1896         try {
1897             // wait for thread to start
1898             started.await();
1899 
1900             // wait for thread to park
1901             await(thread, Thread.State.TIMED_WAITING);
1902         } finally {
1903             done.set(true);
1904             LockSupport.unpark(thread);
1905             thread.join();
1906         }
1907     }
1908 
1909     /**
1910      * Test Thread::isAlive.
1911      */
1912     @Test
1913     void testIsAlive1() throws Exception {
1914         // unstarted
1915         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1916         assertFalse(thread.isAlive());
1917 
1918         // started
1919         thread.start();
1920         try {
1921             assertTrue(thread.isAlive());
1922         } finally {
1923             LockSupport.unpark(thread);
1924             thread.join();
1925         }
1926 
1927         // terminated
1928         assertFalse(thread.isAlive());
1929     }
1930 
1931     /**
1932      * Test Thread.holdsLock when lock not held.
1933      */
1934     @Test
1935     void testHoldsLock1() throws Exception {
1936         VThreadRunner.run(() -> {
1937             var lock = new Object();
1938             assertFalse(Thread.holdsLock(lock));
1939         });
1940     }
1941 
1942     /**
1943      * Test Thread.holdsLock when lock held.
1944      */
1945     @Test
1946     void testHoldsLock2() throws Exception {
1947         VThreadRunner.run(() -> {
1948             var lock = new Object();
1949             synchronized (lock) {
1950                 assertTrue(Thread.holdsLock(lock));
1951             }
1952         });
1953     }
1954 
1955     /**
1956      * Test Thread::getStackTrace on unstarted thread.
1957      */
1958     @Test
1959     void testGetStackTrace1() {
1960         var thread = Thread.ofVirtual().unstarted(() -> { });
1961         StackTraceElement[] stack = thread.getStackTrace();
1962         assertTrue(stack.length == 0);
1963     }
1964 
1965     /**
1966      * Test Thread::getStackTrace on thread that has been started but has not run.
1967      */
1968     @Test
1969     void testGetStackTrace2() throws Exception {
1970         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1971         Executor scheduler = task -> { };
1972         Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1973         Thread thread = builder.start(() -> { });
1974         StackTraceElement[] stack = thread.getStackTrace();
1975         assertTrue(stack.length == 0);
1976     }
1977 
1978     /**
1979      * Test Thread::getStackTrace on running thread.
1980      */
1981     @Test
1982     void testGetStackTrace3() throws Exception {
1983         var sel = Selector.open();
1984         var thread = Thread.ofVirtual().start(() -> {
1985             try { sel.select(); } catch (Exception e) { }
1986         });
1987         try {
1988             while (!contains(thread.getStackTrace(), "select")) {
1989                 assertTrue(thread.isAlive());
1990                 Thread.sleep(20);
1991             }
1992         } finally {
1993             sel.close();
1994             thread.join();
1995         }
1996     }
1997 
1998     /**
1999      * Test Thread::getStackTrace on thread waiting in Object.wait.
2000      */
2001     @Test
2002     void testGetStackTrace4() throws Exception {
2003         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
2004         try (ForkJoinPool pool = new ForkJoinPool(1)) {
2005             AtomicReference<Thread> ref = new AtomicReference<>();
2006             Executor scheduler = task -> {
2007                 pool.submit(() -> {
2008                     ref.set(Thread.currentThread());
2009                     task.run();
2010                 });
2011             };
2012 
2013             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
2014             Thread vthread = builder.start(() -> {
2015                 synchronized (lock) {
2016                     try {
2017                         lock.wait();
2018                     } catch (Exception e) { }
2019                 }
2020             });
2021 
2022             // get carrier Thread
2023             Thread carrier;
2024             while ((carrier = ref.get()) == null) {
2025                 Thread.sleep(20);
2026             }
2027 
2028             // wait for virtual thread to block in wait
2029             await(vthread, Thread.State.WAITING);
2030 
2031             // get stack trace of both carrier and virtual thread
2032             StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
2033             StackTraceElement[] vthreadStackTrace = vthread.getStackTrace();
2034 
2035             // allow virtual thread to terminate
2036             synchronized (lock) {
2037                 lock.notifyAll();
2038             }
2039 
2040             // check carrier thread's stack trace
2041             assertTrue(contains(carrierStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
2042             assertFalse(contains(carrierStackTrace, "java.lang.Object.wait"));
2043 
2044             // check virtual thread's stack trace
2045             assertFalse(contains(vthreadStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
2046             assertTrue(contains(vthreadStackTrace, "java.lang.Object.wait"));
2047         }
2048     }
2049 
2050     /**
2051      * Test Thread::getStackTrace on parked thread.
2052      */
2053     @Test
2054     void testGetStackTrace5() throws Exception {
2055         var thread = Thread.ofVirtual().start(LockSupport::park);
2056         await(thread, Thread.State.WAITING);
2057         try {
2058             StackTraceElement[] stack = thread.getStackTrace();
2059             assertTrue(contains(stack, "LockSupport.park"));
2060         } finally {
2061             LockSupport.unpark(thread);
2062             thread.join();
2063         }
2064     }
2065 
2066     /**
2067      * Test Thread::getStackTrace on terminated thread.
2068      */
2069     @Test
2070     void testGetStackTrace6() throws Exception {
2071         var thread = Thread.ofVirtual().start(() -> { });
2072         thread.join();
2073         StackTraceElement[] stack = thread.getStackTrace();
2074         assertTrue(stack.length == 0);
2075     }
2076 
2077     /**
2078      * Test that Thread.getAllStackTraces does not include virtual threads.
2079      */
2080     @Test
2081     void testGetAllStackTraces1() throws Exception {
2082         VThreadRunner.run(() -> {
2083             Set<Thread> threads = Thread.getAllStackTraces().keySet();
2084             assertFalse(threads.stream().anyMatch(Thread::isVirtual));
2085         });
2086     }
2087 
2088     /**
2089      * Test that Thread.getAllStackTraces includes carrier threads.
2090      */
2091     @Test
2092     void testGetAllStackTraces2() throws Exception {
2093         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
2094         try (ForkJoinPool pool = new ForkJoinPool(1)) {
2095             AtomicReference<Thread> ref = new AtomicReference<>();
2096             Executor scheduler = task -> {
2097                 pool.submit(() -> {
2098                     ref.set(Thread.currentThread());
2099                     task.run();
2100                 });
2101             };
2102 
2103             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
2104             Thread vthread = builder.start(() -> {
2105                 synchronized (lock) {
2106                     try {
2107                         lock.wait();
2108                     } catch (Exception e) { }
2109                 }
2110             });
2111 
2112             // get carrier Thread
2113             Thread carrier;
2114             while ((carrier = ref.get()) == null) {
2115                 Thread.sleep(20);
2116             }
2117 
2118             // wait for virtual thread to block in wait
2119             await(vthread, Thread.State.WAITING);
2120 
2121             // get all stack traces
2122             Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
2123 
2124             // allow virtual thread to terminate
2125             synchronized (lock) {
2126                 lock.notifyAll();
2127             }
2128 
2129             // get stack trace for the carrier thread
2130             StackTraceElement[] stackTrace = map.get(carrier);
2131             assertNotNull(stackTrace);
2132             assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
2133             assertFalse(contains(stackTrace, "java.lang.Object.wait"));
2134 
2135             // there should be no stack trace for the virtual thread
2136             assertNull(map.get(vthread));
2137         }
2138     }
2139 
2140     private boolean contains(StackTraceElement[] stack, String expected) {
2141         return Stream.of(stack)
2142                 .map(Object::toString)
2143                 .anyMatch(s -> s.contains(expected));
2144     }
2145 
2146     /**
2147      * Test Thread::getThreadGroup on virtual thread created by platform thread.
2148      */
2149     @Test
2150     void testThreadGroup1() throws Exception {
2151         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
2152         var vgroup = thread.getThreadGroup();
2153         thread.start();
2154         try {
2155             assertEquals(vgroup, thread.getThreadGroup());
2156         } finally {
2157             LockSupport.unpark(thread);
2158             thread.join();
2159         }
2160         assertNull(thread.getThreadGroup());
2161     }
2162 
2163     /**
2164      * Test Thread::getThreadGroup on platform thread created by virtual thread.
2165      */
2166     @Test
2167     void testThreadGroup2() throws Exception {
2168         VThreadRunner.run(() -> {
2169             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2170             Thread child = new Thread(() -> { });
2171             ThreadGroup group = child.getThreadGroup();
2172             assertEquals(vgroup, group);
2173         });
2174     }
2175 
2176     /**
2177      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2178      * created with 2-arg ThreadGroup constructor.
2179      */
2180     @Test
2181     void testThreadGroup3() throws Exception {
2182         var ref = new AtomicReference<ThreadGroup>();
2183         var thread = Thread.startVirtualThread(() -> {
2184             ref.set(Thread.currentThread().getThreadGroup());
2185         });
2186         thread.join();
2187 
2188         ThreadGroup vgroup = ref.get();
2189         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2190 
2191         ThreadGroup group = new ThreadGroup(vgroup, "group");
2192         assertTrue(group.getParent() == vgroup);
2193         assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
2194 
2195         vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2196         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2197         assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
2198 
2199         vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2200         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2201         assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
2202     }
2203 
2204     /**
2205      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2206      * created with 1-arg ThreadGroup constructor.
2207      */
2208     @Test
2209     void testThreadGroup4() throws Exception {
2210         VThreadRunner.run(() -> {
2211             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2212             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2213 
2214             ThreadGroup group = new ThreadGroup("group");
2215             assertEquals(vgroup, group.getParent());
2216             assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
2217 
2218             vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2219             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2220             assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
2221 
2222             vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2223             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2224             assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
2225         });
2226     }
2227 
2228     /**
2229      * Test Thread.enumerate(false).
2230      */
2231     @Test
2232     void testEnumerate1() throws Exception {
2233         VThreadRunner.run(() -> {
2234             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2235             Thread[] threads = new Thread[100];
2236             int n = vgroup.enumerate(threads, /*recurse*/false);
2237             assertTrue(n == 0);
2238         });
2239     }
2240 
2241     /**
2242      * Test Thread.enumerate(true).
2243      */
2244     @Test
2245     void testEnumerate2() throws Exception {
2246         VThreadRunner.run(() -> {
2247             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2248             Thread[] threads = new Thread[100];
2249             int n = vgroup.enumerate(threads, /*recurse*/true);
2250             assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
2251         });
2252     }
2253 
2254     /**
2255      * Test equals and hashCode.
2256      */
2257     @Test
2258     void testEqualsAndHashCode() throws Exception {
2259         Thread vthread1 = Thread.ofVirtual().unstarted(LockSupport::park);
2260         Thread vthread2 = Thread.ofVirtual().unstarted(LockSupport::park);
2261 
2262         // unstarted
2263         assertTrue(vthread1.equals(vthread1));
2264         assertTrue(vthread2.equals(vthread2));
2265         assertFalse(vthread1.equals(vthread2));
2266         assertFalse(vthread2.equals(vthread1));
2267         int hc1 = vthread1.hashCode();
2268         int hc2 = vthread2.hashCode();
2269 
2270         vthread1.start();
2271         vthread2.start();
2272         try {
2273             // started, maybe running or parked
2274             assertTrue(vthread1.equals(vthread1));
2275             assertTrue(vthread2.equals(vthread2));
2276             assertFalse(vthread1.equals(vthread2));
2277             assertFalse(vthread2.equals(vthread1));
2278             assertTrue(vthread1.hashCode() == hc1);
2279             assertTrue(vthread2.hashCode() == hc2);
2280         } finally {
2281             LockSupport.unpark(vthread1);
2282             LockSupport.unpark(vthread2);
2283         }
2284         vthread1.join();
2285         vthread2.join();
2286 
2287         // terminated
2288         assertTrue(vthread1.equals(vthread1));
2289         assertTrue(vthread2.equals(vthread2));
2290         assertFalse(vthread1.equals(vthread2));
2291         assertFalse(vthread2.equals(vthread1));
2292         assertTrue(vthread1.hashCode() == hc1);
2293         assertTrue(vthread2.hashCode() == hc2);
2294     }
2295 
2296     /**
2297      * Test toString on unstarted thread.
2298      */
2299     @Test
2300     void testToString1() {
2301         Thread thread = Thread.ofVirtual().unstarted(() -> { });
2302         thread.setName("fred");
2303         assertTrue(thread.toString().contains("fred"));
2304     }
2305 
2306     /**
2307      * Test toString on running thread.
2308      */
2309     @Test
2310     void testToString2() throws Exception {
2311         VThreadRunner.run(() -> {
2312             Thread me = Thread.currentThread();
2313             me.setName("fred");
2314             assertTrue(me.toString().contains("fred"));
2315         });
2316     }
2317 
2318     /**
2319      * Test toString on parked thread.
2320      */
2321     @Test
2322     void testToString3() throws Exception {
2323         Thread thread = Thread.ofVirtual().start(() -> {
2324             Thread me = Thread.currentThread();
2325             me.setName("fred");
2326             LockSupport.park();
2327         });
2328         await(thread, Thread.State.WAITING);
2329         try {
2330             assertTrue(thread.toString().contains("fred"));
2331         } finally {
2332             LockSupport.unpark(thread);
2333             thread.join();
2334         }
2335     }
2336 
2337     /**
2338      * Test toString on terminated thread.
2339      */
2340     @Test
2341     void testToString4() throws Exception {
2342         Thread thread = Thread.ofVirtual().start(() -> {
2343             Thread me = Thread.currentThread();
2344             me.setName("fred");
2345         });
2346         thread.join();
2347         assertTrue(thread.toString().contains("fred"));
2348     }
2349 
2350     /**
2351      * Waits for the given thread to reach a given state.
2352      */
2353     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
2354         Thread.State state = thread.getState();
2355         while (state != expectedState) {
2356             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
2357             Thread.sleep(10);
2358             state = thread.getState();
2359         }
2360     }
2361 
2362     /**
2363      * Schedule a thread to be interrupted after a delay.
2364      */
2365     private void scheduleInterrupt(Thread thread, long delayInMillis) {
2366         scheduler.schedule(thread::interrupt, delayInMillis, TimeUnit.MILLISECONDS);
2367     }
2368 }