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