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