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 releases carrier when virtual thread holds a monitor.
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 lock = new Object();
1118             var thread = factory.newThread(() -> {
1119                 list.add("A");
1120                 var child = factory.newThread(() -> {
1121                     list.add("B");
1122                     synchronized (lock) {
1123                         Thread.yield();
1124                     }
1125                     list.add("B");
1126                 });
1127                 child.start();
1128                 Thread.yield();
1129                 list.add("A");
1130                 try { child.join(); } catch (InterruptedException e) { }
1131             });
1132             thread.start();
1133             thread.join();
1134         }
1135         assertEquals(List.of("A", "B", "A", "B"), list);
1136     }
1137 
1138     /**
1139      * Test Thread.yield when thread is pinned by native frame.
1140      */
1141     @Test
1142     void testYield3() throws Exception {
1143         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
1144         var list = new CopyOnWriteArrayList<String>();
1145         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1146             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
1147             var thread = factory.newThread(() -> {
1148                 list.add("A");
1149                 var child = factory.newThread(() -> {
1150                     list.add("B");
1151                 });
1152                 child.start();
1153                 VThreadPinner.runPinned(() -> {
1154                     Thread.yield();   // pinned so will be a no-op
1155                     list.add("A");
1156                 });
1157                 try { child.join(); } catch (InterruptedException e) { }
1158             });
1159             thread.start();
1160             thread.join();
1161         }
1162         assertEquals(List.of("A", "A", "B"), list);
1163     }
1164 
1165     /**
1166      * Test Thread.yield does not consume the thread's parking permit.
1167      */
1168     @Test
1169     void testYield4() throws Exception {
1170         var thread = Thread.ofVirtual().start(() -> {
1171             LockSupport.unpark(Thread.currentThread());
1172             Thread.yield();
1173             LockSupport.park();  // should not park
1174         });
1175         thread.join();
1176     }
1177 
1178     /**
1179      * Test Thread.yield does not make available the thread's parking permit.
1180      */
1181     @Test
1182     void testYield5() throws Exception {
1183         var thread = Thread.ofVirtual().start(() -> {
1184             Thread.yield();
1185             LockSupport.park();  // should park
1186         });
1187         try {
1188             await(thread, Thread.State.WAITING);
1189         } finally {
1190             LockSupport.unpark(thread);
1191             thread.join();
1192         }
1193     }
1194 
1195     /**
1196      * Test Thread.onSpinWait.
1197      */
1198     @Test
1199     void testOnSpinWait() throws Exception {
1200         VThreadRunner.run(() -> {
1201             Thread me = Thread.currentThread();
1202             Thread.onSpinWait();
1203             assertTrue(Thread.currentThread() == me);
1204         });
1205     }
1206 
1207     /**
1208      * Test Thread.sleep(-1).
1209      */
1210     @Test
1211     void testSleep1() throws Exception {
1212         VThreadRunner.run(() -> {
1213             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1));
1214             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1, 0));
1215             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, -1));
1216             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, 1_000_000));
1217         });
1218         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(-1)));
1219     }
1220 
1221     /**
1222      * Test Thread.sleep(0).
1223      */
1224     @Test
1225     void testSleep2() throws Exception {
1226         VThreadRunner.run(() -> Thread.sleep(0));
1227         VThreadRunner.run(() -> Thread.sleep(0, 0));
1228         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(0)));
1229     }
1230 
1231     /**
1232      * Tasks that sleep for 1 second.
1233      */
1234     static Stream<ThrowingRunnable> oneSecondSleepers() {
1235         return Stream.of(
1236                 () -> Thread.sleep(1000),
1237                 () -> Thread.sleep(Duration.ofSeconds(1))
1238         );
1239     }
1240 
1241     /**
1242      * Test Thread.sleep duration.
1243      */
1244     @ParameterizedTest
1245     @MethodSource("oneSecondSleepers")
1246     void testSleep3(ThrowingRunnable sleeper) throws Exception {
1247         VThreadRunner.run(() -> {
1248             long start = millisTime();
1249             sleeper.run();
1250             expectDuration(start, /*min*/900, /*max*/20_000);
1251         });
1252     }
1253 
1254     /**
1255      * Tasks that sleep for zero or longer duration.
1256      */
1257     static Stream<ThrowingRunnable> sleepers() {
1258         return Stream.of(
1259                 () -> Thread.sleep(0),
1260                 () -> Thread.sleep(0, 0),
1261                 () -> Thread.sleep(1000),
1262                 () -> Thread.sleep(1000, 0),
1263                 () -> Thread.sleep(Duration.ofMillis(0)),
1264                 () -> Thread.sleep(Duration.ofMillis(1000))
1265         );
1266     }
1267 
1268     /**
1269      * Test Thread.sleep with interrupt status set.
1270      */
1271     @ParameterizedTest
1272     @MethodSource("sleepers")
1273     void testSleep4(ThrowingRunnable sleeper) throws Exception {
1274         VThreadRunner.run(() -> {
1275             Thread me = Thread.currentThread();
1276             me.interrupt();
1277             try {
1278                 sleeper.run();
1279                 fail("sleep was not interrupted");
1280             } catch (InterruptedException e) {
1281                 // expected
1282                 assertFalse(me.isInterrupted());
1283             }
1284         });
1285     }
1286 
1287     /**
1288      * Test Thread.sleep with interrupt status set and a negative duration.
1289      */
1290     @Test
1291     void testSleep4() throws Exception {
1292         VThreadRunner.run(() -> {
1293             Thread me = Thread.currentThread();
1294             me.interrupt();
1295             Thread.sleep(Duration.ofMillis(-1000));  // does nothing
1296             assertTrue(me.isInterrupted());
1297         });
1298     }
1299 
1300     /**
1301      * Tasks that sleep for a long time.
1302      */
1303     static Stream<ThrowingRunnable> longSleepers() {
1304         return Stream.of(
1305                 () -> Thread.sleep(20_000),
1306                 () -> Thread.sleep(20_000, 0),
1307                 () -> Thread.sleep(Duration.ofSeconds(20))
1308         );
1309     }
1310 
1311     /**
1312      * Test interrupting Thread.sleep.
1313      */
1314     @ParameterizedTest
1315     @MethodSource("longSleepers")
1316     void testSleep5(ThrowingRunnable sleeper) throws Exception {
1317         VThreadRunner.run(() -> {
1318             Thread t = Thread.currentThread();
1319             scheduleInterrupt(t, 100);
1320             try {
1321                 sleeper.run();
1322                 fail("sleep was not interrupted");
1323             } catch (InterruptedException e) {
1324                 // interrupt status should be cleared
1325                 assertFalse(t.isInterrupted());
1326             }
1327         });
1328     }
1329 
1330     /**
1331      * Test that Thread.sleep does not disrupt parking permit.
1332      */
1333     @Test
1334     void testSleep6() throws Exception {
1335         VThreadRunner.run(() -> {
1336             LockSupport.unpark(Thread.currentThread());
1337 
1338             long start = millisTime();
1339             Thread.sleep(1000);
1340             expectDuration(start, /*min*/900, /*max*/20_000);
1341 
1342             // check that parking permit was not consumed
1343             LockSupport.park();
1344         });
1345     }
1346 
1347     /**
1348      * Test that Thread.sleep is not disrupted by unparking thread.
1349      */
1350     @Test
1351     void testSleep7() throws Exception {
1352         AtomicReference<Exception> exc = new AtomicReference<>();
1353         var thread = Thread.ofVirtual().start(() -> {
1354             try {
1355                 long start = millisTime();
1356                 Thread.sleep(1000);
1357                 expectDuration(start, /*min*/900, /*max*/20_000);
1358             } catch (Exception e) {
1359                 exc.set(e);
1360             }
1361 
1362         });
1363         // attempt to disrupt sleep
1364         for (int i = 0; i < 5; i++) {
1365             Thread.sleep(20);
1366             LockSupport.unpark(thread);
1367         }
1368         thread.join();
1369         Exception e = exc.get();
1370         if (e != null) {
1371             throw e;
1372         }
1373     }
1374 
1375     /**
1376      * Test Thread.sleep when pinned.
1377      */
1378     @Test
1379     void testSleep8() throws Exception {
1380         VThreadPinner.runPinned(() -> {
1381             long start = millisTime();
1382             Thread.sleep(1000);
1383             expectDuration(start, /*min*/900, /*max*/20_000);
1384         });
1385     }
1386 
1387     /**
1388      * Test Thread.sleep when pinned and with interrupt status set.
1389      */
1390     @Test
1391     void testSleep9() throws Exception {
1392         VThreadRunner.run(() -> {
1393             Thread me = Thread.currentThread();
1394             me.interrupt();
1395             try {
1396                 VThreadPinner.runPinned(() -> {
1397                     Thread.sleep(2000);
1398                 });
1399                 fail("sleep not interrupted");
1400             } catch (InterruptedException e) {
1401                 // expected
1402                 assertFalse(me.isInterrupted());
1403             }
1404         });
1405     }
1406 
1407     /**
1408      * Test interrupting Thread.sleep when pinned.
1409      */
1410     @Test
1411     void testSleep10() throws Exception {
1412         VThreadRunner.run(() -> {
1413             Thread t = Thread.currentThread();
1414             scheduleInterrupt(t, 100);
1415             try {
1416                 VThreadPinner.runPinned(() -> {
1417                     Thread.sleep(20 * 1000);
1418                 });
1419                 fail("sleep not interrupted");
1420             } catch (InterruptedException e) {
1421                 // interrupt status should be cleared
1422                 assertFalse(t.isInterrupted());
1423             }
1424         });
1425     }
1426 
1427     /**
1428      * Test Thread.sleep(null).
1429      */
1430     @Test
1431     void testSleep11() throws Exception {
1432         assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1433         VThreadRunner.run(() -> {
1434             assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1435         });
1436     }
1437 
1438     /**
1439      * Returns the current time in milliseconds.
1440      */
1441     private static long millisTime() {
1442         long now = System.nanoTime();
1443         return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
1444     }
1445 
1446     /**
1447      * Check the duration of a task
1448      * @param start start time, in milliseconds
1449      * @param min minimum expected duration, in milliseconds
1450      * @param max maximum expected duration, in milliseconds
1451      * @return the duration (now - start), in milliseconds
1452      */
1453     private static void expectDuration(long start, long min, long max) {
1454         long duration = millisTime() - start;
1455         assertTrue(duration >= min,
1456                 "Duration " + duration + "ms, expected >= " + min + "ms");
1457         assertTrue(duration <= max,
1458                 "Duration " + duration + "ms, expected <= " + max + "ms");
1459     }
1460 
1461     /**
1462      * Test Thread.xxxContextClassLoader from the current thread.
1463      */
1464     @Test
1465     void testContextClassLoader1() throws Exception {
1466         ClassLoader loader = new ClassLoader() { };
1467         VThreadRunner.run(() -> {
1468             Thread t = Thread.currentThread();
1469             t.setContextClassLoader(loader);
1470             assertTrue(t.getContextClassLoader() == loader);
1471         });
1472     }
1473 
1474     /**
1475      * Test inheriting initial value of TCCL from platform thread.
1476      */
1477     @Test
1478     void testContextClassLoader2() throws Exception {
1479         ClassLoader loader = new ClassLoader() { };
1480         Thread t = Thread.currentThread();
1481         ClassLoader savedLoader = t.getContextClassLoader();
1482         t.setContextClassLoader(loader);
1483         try {
1484             VThreadRunner.run(() -> {
1485                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1486             });
1487         } finally {
1488             t.setContextClassLoader(savedLoader);
1489         }
1490     }
1491 
1492     /**
1493      * Test inheriting initial value of TCCL from virtual thread.
1494      */
1495     @Test
1496     void testContextClassLoader3() throws Exception {
1497         VThreadRunner.run(() -> {
1498             ClassLoader loader = new ClassLoader() { };
1499             Thread.currentThread().setContextClassLoader(loader);
1500             VThreadRunner.run(() -> {
1501                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1502             });
1503         });
1504     }
1505 
1506     /**
1507      * Test inheriting initial value of TCCL through an intermediate virtual thread.
1508      */
1509     @Test
1510     void testContextClassLoader4() throws Exception {
1511         ClassLoader loader = new ClassLoader() { };
1512         Thread t = Thread.currentThread();
1513         ClassLoader savedLoader = t.getContextClassLoader();
1514         t.setContextClassLoader(loader);
1515         try {
1516             VThreadRunner.run(() -> {
1517                 VThreadRunner.run(() -> {
1518                     assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1519                 });
1520             });
1521         } finally {
1522             t.setContextClassLoader(savedLoader);
1523         }
1524     }
1525 
1526     /**
1527      * Test Thread.xxxContextClassLoader when thread does not inherit the
1528      * initial value of inheritable thread locals.
1529      */
1530     @Test
1531     void testContextClassLoader5() throws Exception {
1532         VThreadRunner.run(() -> {
1533             ClassLoader loader = new ClassLoader() { };
1534             Thread.currentThread().setContextClassLoader(loader);
1535             int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
1536             VThreadRunner.run(characteristics, () -> {
1537                 Thread t = Thread.currentThread();
1538                 assertTrue(t.getContextClassLoader() == ClassLoader.getSystemClassLoader());
1539                 t.setContextClassLoader(loader);
1540                 assertTrue(t.getContextClassLoader() == loader);
1541             });
1542         });
1543     }
1544 
1545     /**
1546      * Test Thread.setUncaughtExceptionHandler.
1547      */
1548     @Test
1549     void testUncaughtExceptionHandler1() throws Exception {
1550         class FooException extends RuntimeException { }
1551         var handler = new CapturingUHE();
1552         Thread thread = Thread.ofVirtual().start(() -> {
1553             Thread me = Thread.currentThread();
1554             assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
1555             me.setUncaughtExceptionHandler(handler);
1556             assertTrue(me.getUncaughtExceptionHandler() == handler);
1557             throw new FooException();
1558         });
1559         thread.join();
1560         assertInstanceOf(FooException.class, handler.exception());
1561         assertEquals(thread, handler.thread());
1562         assertNull(thread.getUncaughtExceptionHandler());
1563     }
1564 
1565     /**
1566      * Test default UncaughtExceptionHandler.
1567      */
1568     @Test
1569     void testUncaughtExceptionHandler2() throws Exception {
1570         class FooException extends RuntimeException { }
1571         var handler = new CapturingUHE();
1572         Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
1573         Thread.setDefaultUncaughtExceptionHandler(handler);
1574         Thread thread;
1575         try {
1576             thread = Thread.ofVirtual().start(() -> {
1577                 Thread me = Thread.currentThread();
1578                 throw new FooException();
1579             });
1580             thread.join();
1581         } finally {
1582             Thread.setDefaultUncaughtExceptionHandler(savedHandler);  // restore
1583         }
1584         assertInstanceOf(FooException.class, handler.exception());
1585         assertEquals(thread, handler.thread());
1586         assertNull(thread.getUncaughtExceptionHandler());
1587     }
1588 
1589     /**
1590      * Test Thread and default UncaughtExceptionHandler set.
1591      */
1592     @Test
1593     void testUncaughtExceptionHandler3() throws Exception {
1594         class FooException extends RuntimeException { }
1595         var defaultHandler = new CapturingUHE();
1596         var threadHandler = new CapturingUHE();
1597         Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
1598         Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
1599         Thread thread;
1600         try {
1601             thread = Thread.ofVirtual().start(() -> {
1602                 Thread me = Thread.currentThread();
1603                 assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
1604                 me.setUncaughtExceptionHandler(threadHandler);
1605                 assertTrue(me.getUncaughtExceptionHandler() == threadHandler);
1606                 throw new FooException();
1607             });
1608             thread.join();
1609         } finally {
1610             Thread.setDefaultUncaughtExceptionHandler(savedHandler);  // restore
1611         }
1612         assertInstanceOf(FooException.class, threadHandler.exception());
1613         assertNull(defaultHandler.exception());
1614         assertEquals(thread, threadHandler.thread());
1615         assertNull(thread.getUncaughtExceptionHandler());
1616     }
1617 
1618     /**
1619      * Test no Thread or default UncaughtExceptionHandler set.
1620      */
1621     @Test
1622     void testUncaughtExceptionHandler4() throws Exception {
1623         Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
1624         Thread.setDefaultUncaughtExceptionHandler(null);
1625         try {
1626             class FooException extends RuntimeException { }
1627             Thread thread = Thread.ofVirtual().start(() -> {
1628                 throw new FooException();
1629             });
1630             thread.join();
1631             assertNull(thread.getUncaughtExceptionHandler());
1632         } finally {
1633             Thread.setDefaultUncaughtExceptionHandler(savedHandler);
1634         }
1635     }
1636 
1637     /**
1638      * Test Thread::threadId and getId.
1639      */
1640     @Test
1641     void testThreadId1() throws Exception {
1642         record ThreadIds(long threadId, long id) { }
1643         var ref = new AtomicReference<ThreadIds>();
1644 
1645         Thread vthread = Thread.ofVirtual().unstarted(() -> {
1646             Thread thread = Thread.currentThread();
1647             ref.set(new ThreadIds(thread.threadId(), thread.getId()));
1648             LockSupport.park();
1649         });
1650 
1651         // unstarted
1652         long tid = vthread.threadId();
1653 
1654         // running
1655         ThreadIds tids;
1656         vthread.start();
1657         try {
1658             while ((tids = ref.get()) == null) {
1659                 Thread.sleep(10);
1660             }
1661             assertTrue(tids.threadId() == tid);
1662             assertTrue(tids.id() == tid);
1663         } finally {
1664             LockSupport.unpark(vthread);
1665             vthread.join();
1666         }
1667 
1668         // terminated
1669         assertTrue(vthread.threadId() == tid);
1670         assertTrue(vthread.getId() == tid);
1671     }
1672 
1673     /**
1674      * Test that each Thread has a unique ID
1675      */
1676     @Test
1677     void testThreadId2() throws Exception {
1678         // thread ID should be unique
1679         long tid1 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1680         long tid2 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1681         long tid3 = Thread.currentThread().threadId();
1682         assertFalse(tid1 == tid2);
1683         assertFalse(tid1 == tid3);
1684         assertFalse(tid2 == tid3);
1685     }
1686 
1687     /**
1688      * Test Thread::getState when thread is new/unstarted.
1689      */
1690     @Test
1691     void testGetState1() {
1692         var thread = Thread.ofVirtual().unstarted(() -> { });
1693         assertEquals(Thread.State.NEW, thread.getState());
1694     }
1695 
1696     /**
1697      * Test Thread::getState when thread is terminated.
1698      */
1699     @Test
1700     void testGetState2() throws Exception {
1701         var thread = Thread.ofVirtual().start(() -> { });
1702         thread.join();
1703         assertEquals(Thread.State.TERMINATED, thread.getState());
1704     }
1705 
1706     /**
1707      * Test Thread::getState when thread is runnable (mounted).
1708      */
1709     @Test
1710     void testGetState3() throws Exception {
1711         var started = new CountDownLatch(1);
1712         var done = new AtomicBoolean();
1713         var thread = Thread.ofVirtual().start(() -> {
1714             started.countDown();
1715 
1716             // spin until done
1717             while (!done.get()) {
1718                 Thread.onSpinWait();
1719             }
1720         });
1721         try {
1722             // wait for thread to start
1723             started.await();
1724 
1725             // thread should be runnable
1726             assertEquals(Thread.State.RUNNABLE, thread.getState());
1727         } finally {
1728             done.set(true);
1729             thread.join();
1730         }
1731     }
1732 
1733     /**
1734      * Test Thread::getState when thread is runnable (not mounted).
1735      */
1736     @Test
1737     void testGetState4() throws Exception {
1738         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
1739         AtomicBoolean completed = new AtomicBoolean();
1740         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1741             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
1742             Thread thread1 = factory.newThread(() -> {
1743                 Thread thread2 = factory.newThread(LockSupport::park);
1744                 assertEquals(Thread.State.NEW, thread2.getState());
1745 
1746                 // start t2 to make it runnable
1747                 thread2.start();
1748                 try {
1749                     assertEquals(Thread.State.RUNNABLE, thread2.getState());
1750 
1751                     // yield to allow t2 to run and park
1752                     Thread.yield();
1753                     assertEquals(Thread.State.WAITING, thread2.getState());
1754                 } finally {
1755                     // unpark t2 to make it runnable again
1756                     LockSupport.unpark(thread2);
1757                 }
1758 
1759                 // t2 should be runnable (not mounted)
1760                 assertEquals(Thread.State.RUNNABLE, thread2.getState());
1761 
1762                 completed.set(true);
1763             });
1764             thread1.start();
1765             thread1.join();
1766         }
1767         assertTrue(completed.get() == true);
1768     }
1769 
1770     /**
1771      * Test Thread::getState when thread is blocked waiting to enter a monitor.
1772      */
1773     @ParameterizedTest
1774     @ValueSource(booleans = { true, false })
1775     void testGetState5(boolean pinned) throws Exception {
1776         var ready = new AtomicBoolean();
1777         var thread = Thread.ofVirtual().unstarted(() -> {
1778             if (pinned) {
1779                 VThreadPinner.runPinned(() -> {
1780                     ready.set(true);
1781                     synchronized (lock) { }
1782                 });
1783             } else {
1784                 ready.set(true);
1785                 synchronized (lock) { }
1786             }
1787         });
1788         synchronized (lock) {
1789             thread.start();
1790             awaitTrue(ready);
1791 
1792             // wait for thread to block
1793             await(thread, Thread.State.BLOCKED);
1794         }
1795         thread.join();
1796     }
1797 
1798     /**
1799      * Test Thread::getState when thread is waiting in Object.wait.
1800      */
1801     @ParameterizedTest
1802     @ValueSource(booleans = { true, false })
1803     void testGetState6(boolean pinned) throws Exception {
1804         var ready = new AtomicBoolean();
1805         var thread = Thread.ofVirtual().start(() -> {
1806             synchronized (lock) {
1807                 try {
1808                     if (pinned) {
1809                         VThreadPinner.runPinned(() -> {
1810                             ready.set(true);
1811                             lock.wait();
1812                         });
1813                     } else {
1814                         ready.set(true);
1815                         lock.wait();
1816                     }
1817                 } catch (InterruptedException e) { }
1818             }
1819         });
1820         try {
1821             // wait for thread to wait
1822             awaitTrue(ready);
1823             await(thread, Thread.State.WAITING);
1824 
1825             // notify, thread should block trying to reenter
1826             synchronized (lock) {
1827                 lock.notifyAll();
1828                 await(thread, Thread.State.BLOCKED);
1829             }
1830         } finally {
1831             thread.interrupt();
1832             thread.join();
1833         }
1834     }
1835 
1836     /**
1837      * Test Thread::getState when thread is waiting in Object.wait(millis).
1838      */
1839     @ParameterizedTest
1840     @ValueSource(booleans = { true, false })
1841     void testGetState7(boolean pinned) throws Exception {
1842         var ready = new AtomicBoolean();
1843         var thread = Thread.ofVirtual().start(() -> {
1844             synchronized (lock) {
1845                 try {
1846                     if (pinned) {
1847                         VThreadPinner.runPinned(() -> {
1848                             ready.set(true);
1849                             lock.wait(Long.MAX_VALUE);
1850                         });
1851                     } else {
1852                         ready.set(true);
1853                         lock.wait(Long.MAX_VALUE);
1854                     }
1855                 } catch (InterruptedException e) { }
1856             }
1857         });
1858         try {
1859             // wait for thread to timed-wait
1860             awaitTrue(ready);
1861             await(thread, Thread.State.TIMED_WAITING);
1862 
1863             // notify, thread should block trying to reenter
1864             synchronized (lock) {
1865                 lock.notifyAll();
1866                 await(thread, Thread.State.BLOCKED);
1867             }
1868         } finally {
1869             thread.interrupt();
1870             thread.join();
1871         }
1872     }
1873 
1874     /**
1875      * Test Thread::getState when thread is parked.
1876      */
1877     @Test
1878     void testGetState8() throws Exception {
1879         var thread = Thread.ofVirtual().start(LockSupport::park);
1880         try {
1881             await(thread, Thread.State.WAITING);
1882         } finally {
1883             LockSupport.unpark(thread);
1884             thread.join();
1885         }
1886     }
1887 
1888     /**
1889      * Test Thread::getState when thread is timed parked.
1890      */
1891     @Test
1892     void testGetState9() throws Exception {
1893         var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE));
1894         try {
1895             await(thread, Thread.State.TIMED_WAITING);
1896         } finally {
1897             LockSupport.unpark(thread);
1898             thread.join();
1899         }
1900     }
1901 
1902     /**
1903      * Test Thread::getState when thread is parked while holding a monitor.
1904      */
1905     @Test
1906     void testGetState10() throws Exception {
1907         var started = new CountDownLatch(1);
1908         var done = new AtomicBoolean();
1909         var thread = Thread.ofVirtual().start(() -> {
1910             started.countDown();
1911             synchronized (lock) {
1912                 while (!done.get()) {
1913                     LockSupport.park();
1914                 }
1915             }
1916         });
1917         try {
1918             // wait for thread to start
1919             started.await();
1920 
1921             // wait for thread to park
1922             await(thread, Thread.State.WAITING);
1923         } finally {
1924             done.set(true);
1925             LockSupport.unpark(thread);
1926             thread.join();
1927         }
1928     }
1929 
1930     /**
1931      * Test Thread::getState when thread is timed parked while holding a monitor.
1932      */
1933     @Test
1934     void testGetState11() throws Exception {
1935         var started = new CountDownLatch(1);
1936         var done = new AtomicBoolean();
1937         var thread = Thread.ofVirtual().start(() -> {
1938             started.countDown();
1939             synchronized (lock) {
1940                 while (!done.get()) {
1941                     LockSupport.parkNanos(Long.MAX_VALUE);
1942                 }
1943             }
1944         });
1945         try {
1946             // wait for thread to start
1947             started.await();
1948 
1949             // wait for thread to park
1950             await(thread, Thread.State.TIMED_WAITING);
1951         } finally {
1952             done.set(true);
1953             LockSupport.unpark(thread);
1954             thread.join();
1955         }
1956     }
1957 
1958     /**
1959      * Test Thread::isAlive.
1960      */
1961     @Test
1962     void testIsAlive1() throws Exception {
1963         // unstarted
1964         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1965         assertFalse(thread.isAlive());
1966 
1967         // started
1968         thread.start();
1969         try {
1970             assertTrue(thread.isAlive());
1971         } finally {
1972             LockSupport.unpark(thread);
1973             thread.join();
1974         }
1975 
1976         // terminated
1977         assertFalse(thread.isAlive());
1978     }
1979 
1980     /**
1981      * Test Thread.holdsLock when lock not held.
1982      */
1983     @Test
1984     void testHoldsLock1() throws Exception {
1985         VThreadRunner.run(() -> {
1986             var lock = new Object();
1987             assertFalse(Thread.holdsLock(lock));
1988         });
1989     }
1990 
1991     /**
1992      * Test Thread.holdsLock when lock held.
1993      */
1994     @Test
1995     void testHoldsLock2() throws Exception {
1996         VThreadRunner.run(() -> {
1997             var lock = new Object();
1998             synchronized (lock) {
1999                 assertTrue(Thread.holdsLock(lock));
2000             }
2001         });
2002     }
2003 
2004     /**
2005      * Test Thread.holdsLock when lock held by carrier thread.
2006      */
2007     @Disabled
2008     @Test
2009     void testHoldsLock3() throws Exception {
2010         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
2011 
2012         Object lock = new Object();
2013 
2014         // carrier thread runs all tasks while holding the lock
2015         ThreadFactory carrierThreadFactory = task -> Thread.ofPlatform().unstarted(() -> {
2016             synchronized (lock) {
2017                 task.run();
2018             }
2019         });
2020         try (ExecutorService pool = Executors.newSingleThreadExecutor(carrierThreadFactory)) {
2021             Executor scheduler = task -> pool.submit(task::run);
2022             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
2023 
2024             // start virtual that tests if it holds the lock
2025             var result = new AtomicReference<Boolean>();
2026             Thread vthread = factory.newThread(() -> {
2027                 result.set(Thread.holdsLock(lock));
2028             });
2029             vthread.start();
2030             vthread.join();
2031             boolean holdsLock = result.get();
2032             assertFalse(holdsLock, "Thread.holdsLock should return false");
2033         }
2034     }
2035 
2036     /**
2037      * Test Thread::getStackTrace on unstarted thread.
2038      */
2039     @Test
2040     void testGetStackTraceUnstarted() {
2041         var thread = Thread.ofVirtual().unstarted(() -> { });
2042         StackTraceElement[] stack = thread.getStackTrace();
2043         assertTrue(stack.length == 0);
2044     }
2045 
2046     /**
2047      * Test Thread::getStackTrace on thread that has been started but has not run.
2048      */
2049     @Test
2050     void testGetStackTraceStarted() throws Exception {
2051         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
2052         Executor scheduler = task -> { };
2053         ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
2054         Thread thread = factory.newThread(() -> { });
2055         thread.start();
2056         StackTraceElement[] stack = thread.getStackTrace();
2057         assertTrue(stack.length == 0);
2058     }
2059 
2060     /**
2061      * Test Thread::getStackTrace on thread that is runnable-mounted.
2062      */
2063     @Test
2064     void testGetStackTraceRunnableMounted() throws Exception {
2065         var ready = new AtomicBoolean();
2066         var done = new AtomicBoolean();
2067 
2068         class Foo {
2069             void spinUntilDone() {
2070                 ready.set(true);
2071                 while (!done.get()) {
2072                     Thread.onSpinWait();
2073                 }
2074             }
2075         }
2076 
2077         Foo foo = new Foo();
2078         var thread = Thread.ofVirtual().start(foo::spinUntilDone);
2079         try {
2080             awaitTrue(ready);
2081             StackTraceElement[] stack = thread.getStackTrace();
2082             assertTrue(contains(stack, Foo.class.getName() + ".spinUntilDone"));
2083         } finally {
2084             done.set(true);
2085             thread.join();
2086         }
2087     }
2088 
2089     /**
2090      * Test Thread::getStackTrace on thread that is runnable-unmounted.
2091      */
2092     @Test
2093     void testGetStackTraceRunnableUnmounted() throws Exception {
2094         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
2095 
2096         // custom scheduler with one carrier thread
2097         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
2098             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
2099 
2100             // start thread1 to park
2101             Thread thread1 = factory.newThread(LockSupport::park);
2102             thread1.start();
2103             await(thread1, Thread.State.WAITING);
2104 
2105             // start thread2 to spin and pin the carrier thread
2106             var started = new AtomicBoolean();
2107             var done = new AtomicBoolean();
2108             Thread thread2 = factory.newThread(() -> {
2109                 started.set(true);
2110                 while (!done.get()) {
2111                     Thread.onSpinWait();
2112                 }
2113             });
2114             thread2.start();
2115             awaitTrue(started);
2116 
2117             try {
2118                 // unpark thread1, it should be "stuck" in runnable state
2119                 // (the carrier thread is pinned, no other virtual thread can run)
2120                 LockSupport.unpark(thread1);
2121                 assertEquals(Thread.State.RUNNABLE, thread1.getState());
2122 
2123                 // print thread1's stack trace
2124                 StackTraceElement[] stack = thread1.getStackTrace();
2125                 assertTrue(contains(stack, "LockSupport.park"));
2126 
2127             } finally {
2128                 done.set(true);
2129             }
2130         }
2131     }
2132 
2133     /**
2134      * Test Thread::getStackTrace on thread blocked on monitor enter.
2135      */
2136     @ParameterizedTest
2137     @ValueSource(booleans = { true, false })
2138     void testGetStackTraceBlocked(boolean pinned) throws Exception {
2139         class Foo {
2140             void enter() {
2141                 synchronized (this) { }
2142             }
2143         }
2144         Foo foo = new Foo();
2145         var ready = new AtomicBoolean();
2146         var thread = Thread.ofVirtual().unstarted(() -> {
2147             if (pinned) {
2148                 VThreadPinner.runPinned(() -> {
2149                     ready.set(true);
2150                     foo.enter();
2151                 });
2152             } else {
2153                 ready.set(true);
2154                 foo.enter();
2155             }
2156         });
2157         synchronized (foo) {
2158             thread.start();
2159             awaitTrue(ready);
2160 
2161             // wait for thread to block
2162             await(thread, Thread.State.BLOCKED);
2163 
2164             StackTraceElement[] stack = thread.getStackTrace();
2165             assertTrue(contains(stack, Foo.class.getName() + ".enter"));
2166         }
2167         thread.join();
2168     }
2169 
2170     /**
2171      * Test Thread::getStackTrace when thread is waiting in Object.wait.
2172      */
2173     @ParameterizedTest
2174     @ValueSource(booleans = { true, false })
2175     void testGetStackTraceWaiting(boolean pinned) throws Exception {
2176         var ready = new AtomicBoolean();
2177         var thread = Thread.ofVirtual().start(() -> {
2178             synchronized (lock) {
2179                 try {
2180                     if (pinned) {
2181                         VThreadPinner.runPinned(() -> {
2182                             ready.set(true);
2183                             lock.wait();
2184                         });
2185                     } else {
2186                         ready.set(true);
2187                         lock.wait();
2188                     }
2189                 } catch (InterruptedException e) { }
2190             }
2191         });
2192         try {
2193             // wait for thread to wait
2194             awaitTrue(ready);
2195             await(thread, Thread.State.WAITING);
2196 
2197             StackTraceElement[] stack = thread.getStackTrace();
2198             assertTrue(contains(stack, "Object.wait"));
2199         } finally {
2200             thread.interrupt();
2201             thread.join();
2202         }
2203     }
2204 
2205     /**
2206      * Test Thread::getStackTrace when thread is waiting in timed-Object.wait.
2207      */
2208     @ParameterizedTest
2209     @ValueSource(booleans = { true, false })
2210     void testGetStackTraceTimedWaiting(boolean pinned) throws Exception {
2211         var ready = new AtomicBoolean();
2212         var thread = Thread.ofVirtual().start(() -> {
2213             synchronized (lock) {
2214                 try {
2215                     if (pinned) {
2216                         VThreadPinner.runPinned(() -> {
2217                             ready.set(true);
2218                             lock.wait(Long.MAX_VALUE);
2219                         });
2220                     } else {
2221                         ready.set(true);
2222                         lock.wait(Long.MAX_VALUE);
2223                     }
2224                 } catch (InterruptedException e) { }
2225             }
2226         });
2227         try {
2228             // wait for thread to wait
2229             awaitTrue(ready);
2230             await(thread, Thread.State.TIMED_WAITING);
2231 
2232             StackTraceElement[] stack = thread.getStackTrace();
2233             assertTrue(contains(stack, "Object.wait"));
2234         } finally {
2235             thread.interrupt();
2236             thread.join();
2237         }
2238     }
2239 
2240     /**
2241      * Test Thread::getStackTrace when thread in park.
2242      */
2243     @ParameterizedTest
2244     @ValueSource(booleans = { true, false })
2245     void testGetStackTraceParked(boolean pinned) throws Exception {
2246         var ready = new AtomicBoolean();
2247         var done = new AtomicBoolean();
2248         var thread = Thread.ofVirtual().start(() -> {
2249             if (pinned) {
2250                 VThreadPinner.runPinned(() -> {
2251                     ready.set(true);
2252                     while (!done.get()) {
2253                         LockSupport.park();
2254                     }
2255                 });
2256             } else {
2257                 ready.set(true);
2258                 while (!done.get()) {
2259                     LockSupport.park();
2260                 }
2261             }
2262         });
2263         try {
2264             // wait for thread to park
2265             awaitTrue(ready);
2266             await(thread, Thread.State.WAITING);
2267 
2268             StackTraceElement[] stack = thread.getStackTrace();
2269             assertTrue(contains(stack, "LockSupport.park"));
2270         } finally {
2271             done.set(true);
2272             LockSupport.unpark(thread);
2273             thread.join();
2274         }
2275     }
2276 
2277     /**
2278      * Test Thread::getStackTrace when thread in timed-park.
2279      */
2280     @ParameterizedTest
2281     @ValueSource(booleans = { true, false })
2282     void testGetStackTraceTimedPark(boolean pinned) throws Exception {
2283         var ready = new AtomicBoolean();
2284         var done = new AtomicBoolean();
2285         var thread = Thread.ofVirtual().start(() -> {
2286             if (pinned) {
2287                 ready.set(true);
2288                 VThreadPinner.runPinned(() -> {
2289                     while (!done.get()) {
2290                         LockSupport.parkNanos(Long.MAX_VALUE);
2291                     }
2292                 });
2293             } else {
2294                 ready.set(true);
2295                 while (!done.get()) {
2296                     LockSupport.parkNanos(Long.MAX_VALUE);
2297                 }
2298             }
2299         });
2300         try {
2301             // wait for thread to park
2302             awaitTrue(ready);
2303             await(thread, Thread.State.TIMED_WAITING);
2304 
2305             StackTraceElement[] stack = thread.getStackTrace();
2306             assertTrue(contains(stack, "LockSupport.parkNanos"));
2307         } finally {
2308             done.set(true);
2309             LockSupport.unpark(thread);
2310             thread.join();
2311         }
2312     }
2313 
2314     /**
2315      * Test Thread::getStackTrace on terminated thread.
2316      */
2317     @Test
2318     void testGetStackTraceTerminated() throws Exception {
2319         var thread = Thread.ofVirtual().start(() -> { });
2320         thread.join();
2321         StackTraceElement[] stack = thread.getStackTrace();
2322         assertTrue(stack.length == 0);
2323     }
2324 
2325     /**
2326      * Test that Thread.getAllStackTraces does not include virtual threads.
2327      */
2328     @Test
2329     void testGetAllStackTraces1() throws Exception {
2330         VThreadRunner.run(() -> {
2331             Set<Thread> threads = Thread.getAllStackTraces().keySet();
2332             assertFalse(threads.stream().anyMatch(Thread::isVirtual));
2333         });
2334     }
2335 
2336     /**
2337      * Test that Thread.getAllStackTraces includes carrier threads.
2338      */
2339     @Test
2340     void testGetAllStackTraces2() throws Exception {
2341         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
2342         try (ForkJoinPool pool = new ForkJoinPool(1)) {
2343             AtomicReference<Thread> ref = new AtomicReference<>();
2344             Executor scheduler = task -> {
2345                 pool.submit(() -> {
2346                     ref.set(Thread.currentThread());
2347                     task.run();
2348                 });
2349             };
2350 
2351             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
2352             Thread vthread = factory.newThread(() -> {
2353                 synchronized (lock) {
2354                     try {
2355                         lock.wait();
2356                     } catch (Exception e) { }
2357                 }
2358             });
2359             vthread.start();
2360 
2361             // get carrier Thread
2362             Thread carrier;
2363             while ((carrier = ref.get()) == null) {
2364                 Thread.sleep(20);
2365             }
2366 
2367             // wait for virtual thread to block in wait
2368             await(vthread, Thread.State.WAITING);
2369 
2370             // get all stack traces
2371             Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
2372 
2373             // allow virtual thread to terminate
2374             synchronized (lock) {
2375                 lock.notifyAll();
2376             }
2377             vthread.join();
2378 
2379             // stack trace for the carrier thread
2380             StackTraceElement[] stackTrace = map.get(carrier);
2381             assertNotNull(stackTrace);
2382             assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
2383             assertFalse(contains(stackTrace, "java.lang.Object.wait"));
2384 
2385             // there should be no stack trace for the virtual thread
2386             assertNull(map.get(vthread));
2387         }
2388     }
2389 
2390     private boolean contains(StackTraceElement[] stack, String expected) {
2391         return Stream.of(stack)
2392                 .map(Object::toString)
2393                 .anyMatch(s -> s.contains(expected));
2394     }
2395 
2396     /**
2397      * Test Thread::getThreadGroup on virtual thread created by platform thread.
2398      */
2399     @Test
2400     void testThreadGroup1() throws Exception {
2401         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
2402         var vgroup = thread.getThreadGroup();
2403         thread.start();
2404         try {
2405             assertEquals(vgroup, thread.getThreadGroup());
2406         } finally {
2407             LockSupport.unpark(thread);
2408             thread.join();
2409         }
2410         assertNull(thread.getThreadGroup());
2411     }
2412 
2413     /**
2414      * Test Thread::getThreadGroup on platform thread created by virtual thread.
2415      */
2416     @Test
2417     void testThreadGroup2() throws Exception {
2418         VThreadRunner.run(() -> {
2419             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2420             Thread child = new Thread(() -> { });
2421             ThreadGroup group = child.getThreadGroup();
2422             assertEquals(vgroup, group);
2423         });
2424     }
2425 
2426     /**
2427      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2428      * created with 2-arg ThreadGroup constructor.
2429      */
2430     @Test
2431     void testThreadGroup3() throws Exception {
2432         var ref = new AtomicReference<ThreadGroup>();
2433         var thread = Thread.startVirtualThread(() -> {
2434             ref.set(Thread.currentThread().getThreadGroup());
2435         });
2436         thread.join();
2437 
2438         ThreadGroup vgroup = ref.get();
2439         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2440 
2441         ThreadGroup group = new ThreadGroup(vgroup, "group");
2442         assertTrue(group.getParent() == vgroup);
2443         assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
2444 
2445         vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2446         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2447         assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
2448 
2449         vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2450         assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2451         assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
2452     }
2453 
2454     /**
2455      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2456      * created with 1-arg ThreadGroup constructor.
2457      */
2458     @Test
2459     void testThreadGroup4() throws Exception {
2460         VThreadRunner.run(() -> {
2461             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2462             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2463 
2464             ThreadGroup group = new ThreadGroup("group");
2465             assertEquals(vgroup, group.getParent());
2466             assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
2467 
2468             vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2469             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2470             assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
2471 
2472             vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2473             assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
2474             assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
2475         });
2476     }
2477 
2478     /**
2479      * Test Thread.enumerate(false).
2480      */
2481     @Test
2482     void testEnumerate1() throws Exception {
2483         VThreadRunner.run(() -> {
2484             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2485             Thread[] threads = new Thread[100];
2486             int n = vgroup.enumerate(threads, /*recurse*/false);
2487             assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
2488         });
2489     }
2490 
2491     /**
2492      * Test Thread.enumerate(true).
2493      */
2494     @Test
2495     void testEnumerate2() throws Exception {
2496         VThreadRunner.run(() -> {
2497             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2498             Thread[] threads = new Thread[100];
2499             int n = vgroup.enumerate(threads, /*recurse*/true);
2500             assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
2501         });
2502     }
2503 
2504     /**
2505      * Test equals and hashCode.
2506      */
2507     @Test
2508     void testEqualsAndHashCode() throws Exception {
2509         Thread vthread1 = Thread.ofVirtual().unstarted(LockSupport::park);
2510         Thread vthread2 = Thread.ofVirtual().unstarted(LockSupport::park);
2511 
2512         // unstarted
2513         assertTrue(vthread1.equals(vthread1));
2514         assertTrue(vthread2.equals(vthread2));
2515         assertFalse(vthread1.equals(vthread2));
2516         assertFalse(vthread2.equals(vthread1));
2517         int hc1 = vthread1.hashCode();
2518         int hc2 = vthread2.hashCode();
2519 
2520         vthread1.start();
2521         vthread2.start();
2522         try {
2523             // started, maybe running or parked
2524             assertTrue(vthread1.equals(vthread1));
2525             assertTrue(vthread2.equals(vthread2));
2526             assertFalse(vthread1.equals(vthread2));
2527             assertFalse(vthread2.equals(vthread1));
2528             assertTrue(vthread1.hashCode() == hc1);
2529             assertTrue(vthread2.hashCode() == hc2);
2530         } finally {
2531             LockSupport.unpark(vthread1);
2532             LockSupport.unpark(vthread2);
2533         }
2534         vthread1.join();
2535         vthread2.join();
2536 
2537         // terminated
2538         assertTrue(vthread1.equals(vthread1));
2539         assertTrue(vthread2.equals(vthread2));
2540         assertFalse(vthread1.equals(vthread2));
2541         assertFalse(vthread2.equals(vthread1));
2542         assertTrue(vthread1.hashCode() == hc1);
2543         assertTrue(vthread2.hashCode() == hc2);
2544     }
2545 
2546     /**
2547      * Test toString on unstarted thread.
2548      */
2549     @Test
2550     void testToString1() {
2551         Thread thread = Thread.ofVirtual().unstarted(() -> { });
2552         thread.setName("fred");
2553         assertTrue(thread.toString().contains("fred"));
2554     }
2555 
2556     /**
2557      * Test toString on running thread.
2558      */
2559     @Test
2560     void testToString2() throws Exception {
2561         VThreadRunner.run(() -> {
2562             Thread me = Thread.currentThread();
2563             me.setName("fred");
2564             assertTrue(me.toString().contains("fred"));
2565         });
2566     }
2567 
2568     /**
2569      * Test toString on parked thread.
2570      */
2571     @Test
2572     void testToString3() throws Exception {
2573         Thread thread = Thread.ofVirtual().start(() -> {
2574             Thread me = Thread.currentThread();
2575             me.setName("fred");
2576             LockSupport.park();
2577         });
2578         await(thread, Thread.State.WAITING);
2579         try {
2580             assertTrue(thread.toString().contains("fred"));
2581         } finally {
2582             LockSupport.unpark(thread);
2583             thread.join();
2584         }
2585     }
2586 
2587     /**
2588      * Test toString on terminated thread.
2589      */
2590     @Test
2591     void testToString4() throws Exception {
2592         Thread thread = Thread.ofVirtual().start(() -> {
2593             Thread me = Thread.currentThread();
2594             me.setName("fred");
2595         });
2596         thread.join();
2597         assertTrue(thread.toString().contains("fred"));
2598     }
2599 
2600     /**
2601      * Thread.UncaughtExceptionHandler that captures the first exception thrown.
2602      */
2603     private static class CapturingUHE implements Thread.UncaughtExceptionHandler {
2604         Thread thread;
2605         Throwable exception;
2606         @Override
2607         public void uncaughtException(Thread t, Throwable e) {
2608             synchronized (this) {
2609                 if (thread == null) {
2610                     this.thread = t;
2611                     this.exception = e;
2612                 }
2613             }
2614         }
2615         Thread thread() {
2616             synchronized (this) {
2617                 return thread;
2618             }
2619         }
2620         Throwable exception() {
2621             synchronized (this) {
2622                 return exception;
2623             }
2624         }
2625     }
2626 
2627     /**
2628      * Waits for the boolean value to become true.
2629      */
2630     private static void awaitTrue(AtomicBoolean ref) throws Exception {
2631         while (!ref.get()) {
2632             Thread.sleep(20);
2633         }
2634     }
2635 
2636     /**
2637      * Waits for the given thread to reach a given state.
2638      */
2639     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
2640         Thread.State state = thread.getState();
2641         while (state != expectedState) {
2642             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
2643             Thread.sleep(10);
2644             state = thread.getState();
2645         }
2646     }
2647 
2648     /**
2649      * Schedule a thread to be interrupted after a delay.
2650      */
2651     private void scheduleInterrupt(Thread thread, long delayInMillis) {
2652         scheduler.schedule(thread::interrupt, delayInMillis, TimeUnit.MILLISECONDS);
2653     }
2654 }