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