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