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
  27  * @summary Test Thread API with virtual threads
  28  * @enablePreview
  29  * @modules java.base/java.lang:+open
  30  * @library /test/lib
  31  * @run junit ThreadAPI
  32  */
  33 
  34 /*
  35  * @test id=no-vmcontinuations
  36  * @requires vm.continuations
  37  * @enablePreview
  38  * @modules java.base/java.lang:+open
  39  * @library /test/lib
  40  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ThreadAPI
  41  */
  42 
  43 import java.time.Duration;
  44 import java.util.Arrays;
  45 import java.util.ArrayList;
  46 import java.util.List;
  47 import java.util.Map;
  48 import java.util.Set;
  49 import java.util.concurrent.CopyOnWriteArrayList;
  50 import java.util.concurrent.CountDownLatch;
  51 import java.util.concurrent.ExecutorService;
  52 import java.util.concurrent.Executor;
  53 import java.util.concurrent.Executors;
  54 import java.util.concurrent.ForkJoinPool;
  55 import java.util.concurrent.ScheduledExecutorService;
  56 import java.util.concurrent.ThreadFactory;
  57 import java.util.concurrent.TimeUnit;
  58 import java.util.concurrent.atomic.AtomicBoolean;
  59 import java.util.concurrent.atomic.AtomicReference;
  60 import java.util.concurrent.locks.LockSupport;
  61 import java.util.concurrent.locks.ReentrantLock;
  62 import java.util.stream.Stream;
  63 import java.nio.channels.Selector;
  64 
  65 import jdk.test.lib.thread.VThreadRunner;
  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         awaitParked(thread);
 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             awaitBlocked(thread);
 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             awaitParked(thread);
 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             synchronized (lock) {
 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         }
 771     }
 772 
 773     /**
 774      * Test virtual thread invoking timed-Thread.join on a thread that is parking
 775      * and unparking while pinned.
 776      */
 777     @Test
 778     void testJoin34() throws Exception {
 779         // need at least two carrier threads due to pinning
 780         int previousParallelism = VThreadRunner.ensureParallelism(2);
 781         try {
 782             VThreadRunner.run(this::testJoin33);
 783         } finally {
 784             // restore
 785             VThreadRunner.setParallelism(previousParallelism);
 786         }
 787     }
 788 
 789     /**
 790      * Test Thread.join(null).
 791      */
 792     @Test
 793     void testJoin35() throws Exception {
 794         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
 795 
 796         // unstarted
 797         assertThrows(NullPointerException.class, () -> thread.join(null));
 798 
 799         // started
 800         thread.start();
 801         try {
 802             assertThrows(NullPointerException.class, () -> thread.join(null));
 803         } finally {
 804             LockSupport.unpark(thread);
 805         }
 806         thread.join();
 807 
 808         // terminated
 809         assertThrows(NullPointerException.class, () -> thread.join(null));
 810     }
 811 
 812     /**
 813      * Test Thread.interrupt on current thread.
 814      */
 815     @Test
 816     void testInterrupt1() throws Exception {
 817         VThreadRunner.run(() -> {
 818             Thread me = Thread.currentThread();
 819             assertFalse(me.isInterrupted());
 820             me.interrupt();
 821             assertTrue(me.isInterrupted());
 822             Thread.interrupted();  // clear interrupt status
 823             assertFalse(me.isInterrupted());
 824             me.interrupt();
 825         });
 826     }
 827 
 828     /**
 829      * Test Thread.interrupt before thread started.
 830      */
 831     @Test
 832     void testInterrupt2() throws Exception {
 833         var thread = Thread.ofVirtual().unstarted(() -> { });
 834         thread.interrupt();
 835         assertTrue(thread.isInterrupted());
 836     }
 837 
 838     /**
 839      * Test Thread.interrupt after thread started.
 840      */
 841     @Test
 842     void testInterrupt3() throws Exception {
 843         var thread = Thread.ofVirtual().start(() -> { });
 844         thread.join();
 845         thread.interrupt();
 846         assertTrue(thread.isInterrupted());
 847     }
 848 
 849     /**
 850      * Test termination with interrupt status set.
 851      */
 852     @Test
 853     void testInterrupt4() throws Exception {
 854         var thread = Thread.ofVirtual().start(() -> {
 855             Thread.currentThread().interrupt();
 856         });
 857         thread.join();
 858         assertTrue(thread.isInterrupted());
 859     }
 860 
 861     /**
 862      * Test Thread.interrupt of thread blocked in Selector.select.
 863      */
 864     @Test
 865     void testInterrupt5() throws Exception {
 866         var exception = new AtomicReference<Exception>();
 867         var thread = Thread.ofVirtual().start(() -> {
 868             try {
 869                 try (var sel = Selector.open()) {
 870                     sel.select();
 871                     assertTrue(Thread.currentThread().isInterrupted());
 872                 }
 873             } catch (Exception e) {
 874                 exception.set(e);
 875             }
 876         });
 877         Thread.sleep(100);  // give time for thread to block
 878         thread.interrupt();
 879         thread.join();
 880         assertNull(exception.get());
 881     }
 882 
 883     /**
 884      * Test Thread.interrupt of thread parked in sleep.
 885      */
 886     @Test
 887     void testInterrupt6() throws Exception {
 888         var exception = new AtomicReference<Exception>();
 889         var thread = Thread.ofVirtual().start(() -> {
 890             try {
 891                 try {
 892                     Thread.sleep(60*1000);
 893                     fail("sleep not interrupted");
 894                 } catch (InterruptedException e) {
 895                     // interrupt status should be reset
 896                     assertFalse(Thread.interrupted());
 897                 }
 898             } catch (Exception e) {
 899                 exception.set(e);
 900             }
 901         });
 902         awaitParked(thread);
 903         thread.interrupt();
 904         thread.join();
 905         assertNull(exception.get());
 906     }
 907 
 908     /**
 909      * Test Thread.interrupt of parked thread.
 910      */
 911     @Test
 912     void testInterrupt7() throws Exception {
 913         var exception = new AtomicReference<Exception>();
 914         var thread = Thread.ofVirtual().start(() -> {
 915             try {
 916                 LockSupport.park();
 917                 assertTrue(Thread.currentThread().isInterrupted());
 918             } catch (Exception e) {
 919                 exception.set(e);
 920             }
 921         });
 922         awaitParked(thread);
 923         thread.interrupt();
 924         thread.join();
 925         assertNull(exception.get());
 926     }
 927 
 928     /**
 929      * Test trying to park with interrupt status set.
 930      */
 931     @Test
 932     void testInterrupt8() throws Exception {
 933         VThreadRunner.run(() -> {
 934             Thread me = Thread.currentThread();
 935             me.interrupt();
 936             LockSupport.park();
 937             assertTrue(Thread.interrupted());
 938         });
 939     }
 940 
 941     /**
 942      * Test trying to wait with interrupt status set.
 943      */
 944     @Test
 945     void testInterrupt9() throws Exception {
 946         VThreadRunner.run(() -> {
 947             Thread me = Thread.currentThread();
 948             me.interrupt();
 949             synchronized (lock) {
 950                 try {
 951                     lock.wait();
 952                     fail("wait not interrupted");
 953                 } catch (InterruptedException expected) {
 954                     assertFalse(Thread.interrupted());
 955                 }
 956             }
 957         });
 958     }
 959 
 960     /**
 961      * Test trying to block with interrupt status set.
 962      */
 963     @Test
 964     void testInterrupt10() throws Exception {
 965         VThreadRunner.run(() -> {
 966             Thread me = Thread.currentThread();
 967             me.interrupt();
 968             try (Selector sel = Selector.open()) {
 969                 sel.select();
 970                 assertTrue(Thread.interrupted());
 971             }
 972         });
 973     }
 974 
 975     /**
 976      * Test Thread.getName and setName from current thread, started without name.
 977      */
 978     @Test
 979     void testSetName1() throws Exception {
 980         VThreadRunner.run(() -> {
 981             Thread me = Thread.currentThread();
 982             assertTrue(me.getName().isEmpty());
 983             me.setName("fred");
 984             assertEquals("fred", me.getName());
 985         });
 986     }
 987 
 988     /**
 989      * Test Thread.getName and setName from current thread, started with name.
 990      */
 991     @Test
 992     void testSetName2() throws Exception {
 993         VThreadRunner.run("fred", () -> {
 994             Thread me = Thread.currentThread();
 995             assertEquals("fred", me.getName());
 996             me.setName("joe");
 997             assertEquals("joe", me.getName());
 998         });
 999     }
1000 
1001     /**
1002      * Test Thread.getName and setName from another thread.
1003      */
1004     @Test
1005     void testSetName3() throws Exception {
1006         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1007         assertTrue(thread.getName().isEmpty());
1008 
1009         // not started
1010         thread.setName("fred1");
1011         assertEquals("fred1", thread.getName());
1012 
1013         // started
1014         thread.start();
1015         try {
1016             assertEquals("fred1", thread.getName());
1017             thread.setName("fred2");
1018             assertEquals("fred2", thread.getName());
1019         } finally {
1020             LockSupport.unpark(thread);
1021             thread.join();
1022         }
1023 
1024         // terminated
1025         assertEquals("fred2", thread.getName());
1026         thread.setName("fred3");
1027         assertEquals("fred3", thread.getName());
1028     }
1029 
1030     /**
1031      * Test Thread.getPriority and setPriority from current thread.
1032      */
1033     @Test
1034     void testSetPriority1() throws Exception {
1035         VThreadRunner.run(() -> {
1036             Thread me = Thread.currentThread();
1037             assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
1038 
1039             me.setPriority(Thread.MAX_PRIORITY);
1040             assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
1041 
1042             me.setPriority(Thread.NORM_PRIORITY);
1043             assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
1044 
1045             me.setPriority(Thread.MIN_PRIORITY);
1046             assertTrue(me.getPriority() == Thread.NORM_PRIORITY);
1047 
1048             assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
1049         });
1050     }
1051 
1052     /**
1053      * Test Thread.getPriority and setPriority from another thread.
1054      */
1055     @Test
1056     void testSetPriority2() throws Exception {
1057         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1058 
1059         // not started
1060         assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1061 
1062         thread.setPriority(Thread.MAX_PRIORITY);
1063         assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1064 
1065         thread.setPriority(Thread.NORM_PRIORITY);
1066         assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1067 
1068         thread.setPriority(Thread.MIN_PRIORITY);
1069         assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1070 
1071         assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
1072 
1073         // running
1074         thread.start();
1075         try {
1076             assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1077             thread.setPriority(Thread.NORM_PRIORITY);
1078 
1079             thread.setPriority(Thread.MAX_PRIORITY);
1080             assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1081 
1082             thread.setPriority(Thread.NORM_PRIORITY);
1083             assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1084 
1085             thread.setPriority(Thread.MIN_PRIORITY);
1086             assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1087 
1088             assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
1089 
1090         } finally {
1091             LockSupport.unpark(thread);
1092         }
1093         thread.join();
1094 
1095         // terminated
1096         assertTrue(thread.getPriority() == Thread.NORM_PRIORITY);
1097     }
1098 
1099     /**
1100      * Test Thread.isDaemon and setDaemon from current thread.
1101      */
1102     @Test
1103     void testSetDaemon1() throws Exception {
1104         VThreadRunner.run(() -> {
1105             Thread me = Thread.currentThread();
1106             assertTrue(me.isDaemon());
1107             assertThrows(IllegalThreadStateException.class, () -> me.setDaemon(true));
1108             assertThrows(IllegalArgumentException.class, () -> me.setDaemon(false));
1109         });
1110     }
1111 
1112     /**
1113      * Test Thread.isDaemon and setDaemon from another thread.
1114      */
1115     @Test
1116     void testSetDaemon2() throws Exception {
1117         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1118 
1119         // not started
1120         assertTrue(thread.isDaemon());
1121         thread.setDaemon(true);
1122         assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
1123 
1124         // running
1125         thread.start();
1126         try {
1127             assertTrue(thread.isDaemon());
1128             assertThrows(IllegalThreadStateException.class, () -> thread.setDaemon(true));
1129             assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
1130         } finally {
1131             LockSupport.unpark(thread);
1132         }
1133         thread.join();
1134 
1135         // terminated
1136         assertTrue(thread.isDaemon());
1137     }
1138 
1139     /**
1140      * Test Thread.yield releases thread when not pinned.
1141      */
1142     @Test
1143     void testYield1() throws Exception {
1144         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1145         var list = new CopyOnWriteArrayList<String>();
1146         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1147             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1148             ThreadFactory factory = builder.factory();
1149             var thread = factory.newThread(() -> {
1150                 list.add("A");
1151                 var child = factory.newThread(() -> {
1152                     list.add("B");
1153                     Thread.yield();
1154                     list.add("B");
1155                 });
1156                 child.start();
1157                 Thread.yield();
1158                 list.add("A");
1159                 try { child.join(); } catch (InterruptedException e) { }
1160             });
1161             thread.start();
1162             thread.join();
1163         }
1164         assertEquals(List.of("A", "B", "A", "B"), list);
1165     }
1166 
1167     /**
1168      * Test Thread.yield when thread is pinned.
1169      */
1170     @Test
1171     void testYield2() throws Exception {
1172         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1173         var list = new CopyOnWriteArrayList<String>();
1174         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1175             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1176             ThreadFactory factory = builder.factory();
1177             var thread = factory.newThread(() -> {
1178                 list.add("A");
1179                 var child = factory.newThread(() -> {
1180                     list.add("B");
1181                 });
1182                 child.start();
1183                 synchronized (lock) {
1184                     Thread.yield();   // pinned so will be a no-op
1185                     list.add("A");
1186                 }
1187                 try { child.join(); } catch (InterruptedException e) { }
1188             });
1189             thread.start();
1190             thread.join();
1191         }
1192         assertEquals(List.of("A", "A", "B"), list);
1193     }
1194 
1195     /**
1196      * Test Thread.onSpinWait.
1197      */
1198     @Test
1199     void testOnSpinWait() throws Exception {
1200         VThreadRunner.run(() -> {
1201             Thread me = Thread.currentThread();
1202             Thread.onSpinWait();
1203             assertTrue(Thread.currentThread() == me);
1204         });
1205     }
1206 
1207     /**
1208      * Test Thread.sleep(-1).
1209      */
1210     @Test
1211     void testSleep1() throws Exception {
1212         VThreadRunner.run(() -> {
1213             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1));
1214             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1, 0));
1215             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, -1));
1216             assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, 1_000_000));
1217         });
1218         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(-1)));
1219     }
1220 
1221     /**
1222      * Test Thread.sleep(0).
1223      */
1224     @Test
1225     void testSleep2() throws Exception {
1226         VThreadRunner.run(() -> Thread.sleep(0));
1227         VThreadRunner.run(() -> Thread.sleep(0, 0));
1228         VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(0)));
1229     }
1230 
1231     /**
1232      * Tasks that sleep for 1 second.
1233      */
1234     static Stream<ThrowingRunnable> oneSecondSleepers() {
1235         return Stream.of(
1236                 () -> Thread.sleep(1000),
1237                 () -> Thread.sleep(Duration.ofSeconds(1))
1238         );
1239     }
1240 
1241     /**
1242      * Test Thread.sleep duration.
1243      */
1244     @ParameterizedTest
1245     @MethodSource("oneSecondSleepers")
1246     void testSleep3(ThrowingRunnable sleeper) throws Exception {
1247         VThreadRunner.run(() -> {
1248             long start = millisTime();
1249             sleeper.run();
1250             expectDuration(start, /*min*/900, /*max*/4000);
1251         });
1252     }
1253 
1254     /**
1255      * Tasks that sleep for zero or longer duration.
1256      */
1257     static Stream<ThrowingRunnable> sleepers() {
1258         return Stream.of(
1259                 () -> Thread.sleep(0),
1260                 () -> Thread.sleep(0, 0),
1261                 () -> Thread.sleep(1000),
1262                 () -> Thread.sleep(1000, 0),
1263                 () -> Thread.sleep(Duration.ofMillis(0)),
1264                 () -> Thread.sleep(Duration.ofMillis(1000))
1265         );
1266     }
1267 
1268     /**
1269      * Test Thread.sleep with interrupt status set.
1270      */
1271     @ParameterizedTest
1272     @MethodSource("sleepers")
1273     void testSleep4(ThrowingRunnable sleeper) throws Exception {
1274         VThreadRunner.run(() -> {
1275             Thread me = Thread.currentThread();
1276             me.interrupt();
1277             try {
1278                 sleeper.run();
1279                 fail("sleep was not interrupted");
1280             } catch (InterruptedException e) {
1281                 // expected
1282                 assertFalse(me.isInterrupted());
1283             }
1284         });
1285     }
1286 
1287     /**
1288      * Test Thread.sleep with interrupt status set and a negative duration.
1289      */
1290     @Test
1291     void testSleep4() throws Exception {
1292         VThreadRunner.run(() -> {
1293             Thread me = Thread.currentThread();
1294             me.interrupt();
1295             Thread.sleep(Duration.ofMillis(-1000));  // does nothing
1296             assertTrue(me.isInterrupted());
1297         });
1298     }
1299 
1300     /**
1301      * Tasks that sleep for a long time.
1302      */
1303     static Stream<ThrowingRunnable> longSleepers() {
1304         return Stream.of(
1305                 () -> Thread.sleep(20_000),
1306                 () -> Thread.sleep(20_000, 0),
1307                 () -> Thread.sleep(Duration.ofSeconds(20))
1308         );
1309     }
1310 
1311     /**
1312      * Test interrupting Thread.sleep.
1313      */
1314     @ParameterizedTest
1315     @MethodSource("longSleepers")
1316     void testSleep5(ThrowingRunnable sleeper) throws Exception {
1317         VThreadRunner.run(() -> {
1318             Thread t = Thread.currentThread();
1319             scheduleInterrupt(t, 100);
1320             try {
1321                 sleeper.run();
1322                 fail("sleep was not interrupted");
1323             } catch (InterruptedException e) {
1324                 // interrupt status should be cleared
1325                 assertFalse(t.isInterrupted());
1326             }
1327         });
1328     }
1329 
1330     /**
1331      * Test that Thread.sleep does not disrupt parking permit.
1332      */
1333     @Test
1334     void testSleep6() throws Exception {
1335         VThreadRunner.run(() -> {
1336             LockSupport.unpark(Thread.currentThread());
1337 
1338             long start = millisTime();
1339             Thread.sleep(1000);
1340             expectDuration(start, /*min*/900, /*max*/4000);
1341 
1342             // check that parking permit was not consumed
1343             LockSupport.park();
1344         });
1345     }
1346 
1347     /**
1348      * Test that Thread.sleep is not disrupted by unparking thread.
1349      */
1350     @Test
1351     void testSleep7() throws Exception {
1352         AtomicReference<Exception> exc = new AtomicReference<>();
1353         var thread = Thread.ofVirtual().start(() -> {
1354             try {
1355                 long start = millisTime();
1356                 Thread.sleep(1000);
1357                 expectDuration(start, /*min*/900, /*max*/4000);
1358             } catch (Exception e) {
1359                 exc.set(e);
1360             }
1361 
1362         });
1363         // attempt to disrupt sleep
1364         for (int i = 0; i < 5; i++) {
1365             Thread.sleep(20);
1366             LockSupport.unpark(thread);
1367         }
1368         thread.join();
1369         Exception e = exc.get();
1370         if (e != null) {
1371             throw e;
1372         }
1373     }
1374 
1375     /**
1376      * Test Thread.sleep when pinned.
1377      */
1378     @Test
1379     void testSleep8() throws Exception {
1380         VThreadRunner.run(() -> {
1381             long start = millisTime();
1382             synchronized (lock) {
1383                 Thread.sleep(1000);
1384             }
1385             expectDuration(start, /*min*/900, /*max*/4000);
1386         });
1387     }
1388 
1389     /**
1390      * Test Thread.sleep when pinned and with interrupt status set.
1391      */
1392     @Test
1393     void testSleep9() throws Exception {
1394         VThreadRunner.run(() -> {
1395             Thread me = Thread.currentThread();
1396             me.interrupt();
1397             try {
1398                 synchronized (lock) {
1399                     Thread.sleep(2000);
1400                 }
1401                 fail("sleep not interrupted");
1402             } catch (InterruptedException e) {
1403                 // expected
1404                 assertFalse(me.isInterrupted());
1405             }
1406         });
1407     }
1408 
1409     /**
1410      * Test interrupting Thread.sleep when pinned.
1411      */
1412     @Test
1413     void testSleep10() throws Exception {
1414         VThreadRunner.run(() -> {
1415             Thread t = Thread.currentThread();
1416             scheduleInterrupt(t, 100);
1417             try {
1418                 synchronized (lock) {
1419                     Thread.sleep(20 * 1000);
1420                 }
1421                 fail("sleep not interrupted");
1422             } catch (InterruptedException e) {
1423                 // interrupt status should be cleared
1424                 assertFalse(t.isInterrupted());
1425             }
1426         });
1427     }
1428 
1429     /**
1430      * Test Thread.sleep(null).
1431      */
1432     @Test
1433     void testSleep11() throws Exception {
1434         assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1435         VThreadRunner.run(() -> {
1436             assertThrows(NullPointerException.class, () -> Thread.sleep(null));
1437         });
1438     }
1439 
1440     /**
1441      * Returns the current time in milliseconds.
1442      */
1443     private static long millisTime() {
1444         long now = System.nanoTime();
1445         return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
1446     }
1447 
1448     /**
1449      * Check the duration of a task
1450      * @param start start time, in milliseconds
1451      * @param min minimum expected duration, in milliseconds
1452      * @param max maximum expected duration, in milliseconds
1453      * @return the duration (now - start), in milliseconds
1454      */
1455     private static void expectDuration(long start, long min, long max) {
1456         long duration = millisTime() - start;
1457         assertTrue(duration >= min,
1458                 "Duration " + duration + "ms, expected >= " + min + "ms");
1459         assertTrue(duration <= max,
1460                 "Duration " + duration + "ms, expected <= " + max + "ms");
1461     }
1462 
1463     /**
1464      * Test Thread.xxxContextClassLoader from the current thread.
1465      */
1466     @Test
1467     void testContextClassLoader1() throws Exception {
1468         ClassLoader loader = new ClassLoader() { };
1469         VThreadRunner.run(() -> {
1470             Thread t = Thread.currentThread();
1471             t.setContextClassLoader(loader);
1472             assertTrue(t.getContextClassLoader() == loader);
1473         });
1474     }
1475 
1476     /**
1477      * Test inheriting initial value of TCCL from platform thread.
1478      */
1479     @Test
1480     void testContextClassLoader2() throws Exception {
1481         ClassLoader loader = new ClassLoader() { };
1482         Thread t = Thread.currentThread();
1483         ClassLoader savedLoader = t.getContextClassLoader();
1484         t.setContextClassLoader(loader);
1485         try {
1486             VThreadRunner.run(() -> {
1487                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1488             });
1489         } finally {
1490             t.setContextClassLoader(savedLoader);
1491         }
1492     }
1493 
1494     /**
1495      * Test inheriting initial value of TCCL from virtual thread.
1496      */
1497     @Test
1498     void testContextClassLoader3() throws Exception {
1499         VThreadRunner.run(() -> {
1500             ClassLoader loader = new ClassLoader() { };
1501             Thread.currentThread().setContextClassLoader(loader);
1502             VThreadRunner.run(() -> {
1503                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1504             });
1505         });
1506     }
1507 
1508     /**
1509      * Test inheriting initial value of TCCL through an intermediate virtual thread.
1510      */
1511     @Test
1512     void testContextClassLoader4() 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                 VThreadRunner.run(() -> {
1520                     assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1521                 });
1522             });
1523         } finally {
1524             t.setContextClassLoader(savedLoader);
1525         }
1526     }
1527 
1528     /**
1529      * Test Thread.xxxContextClassLoader when thread locals not supported.
1530      */
1531     @Test
1532     void testContextClassLoader5() throws Exception {
1533         ClassLoader scl = ClassLoader.getSystemClassLoader();
1534         ClassLoader loader = new ClassLoader() { };
1535         VThreadRunner.run(VThreadRunner.NO_THREAD_LOCALS, () -> {
1536             Thread t = Thread.currentThread();
1537             assertTrue(t.getContextClassLoader() == scl);
1538             assertThrows(UnsupportedOperationException.class,
1539                          () -> t.setContextClassLoader(loader));
1540             assertTrue(t.getContextClassLoader() == scl);
1541         });
1542     }
1543 
1544     /**
1545      * Test Thread.xxxContextClassLoader when thread does not inherit the
1546      * initial value of inheritable thread locals.
1547      */
1548     @Test
1549     void testContextClassLoader6() throws Exception {
1550         VThreadRunner.run(() -> {
1551             ClassLoader loader = new ClassLoader() { };
1552             Thread.currentThread().setContextClassLoader(loader);
1553             int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
1554             VThreadRunner.run(characteristics, () -> {
1555                 Thread t = Thread.currentThread();
1556                 assertTrue(t.getContextClassLoader() == ClassLoader.getSystemClassLoader());
1557                 t.setContextClassLoader(loader);
1558                 assertTrue(t.getContextClassLoader() == loader);
1559             });
1560         });
1561     }
1562 
1563     /**
1564      * Test Thread.setUncaughtExceptionHandler.
1565      */
1566     @Test
1567     void testUncaughtExceptionHandler1() throws Exception {
1568         class FooException extends RuntimeException { }
1569         var exception = new AtomicReference<Throwable>();
1570         Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
1571         Thread thread = Thread.ofVirtual().start(() -> {
1572             Thread me = Thread.currentThread();
1573             assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
1574             me.setUncaughtExceptionHandler(handler);
1575             assertTrue(me.getUncaughtExceptionHandler() == handler);
1576             throw new FooException();
1577         });
1578         thread.join();
1579         assertTrue(exception.get() instanceof FooException);
1580         assertNull(thread.getUncaughtExceptionHandler());
1581     }
1582 
1583     /**
1584      * Test default UncaughtExceptionHandler.
1585      */
1586     @Test
1587     void testUncaughtExceptionHandler2() throws Exception {
1588         class FooException extends RuntimeException { }
1589         var exception = new AtomicReference<Throwable>();
1590         Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
1591         Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
1592         Thread.setDefaultUncaughtExceptionHandler(handler);
1593         Thread thread;
1594         try {
1595             thread = Thread.ofVirtual().start(() -> {
1596                 Thread me = Thread.currentThread();
1597                 throw new FooException();
1598             });
1599             thread.join();
1600         } finally {
1601             Thread.setDefaultUncaughtExceptionHandler(savedHandler);
1602         }
1603         assertTrue(exception.get() instanceof FooException);
1604         assertNull(thread.getUncaughtExceptionHandler());
1605     }
1606 
1607     /**
1608      * Test no UncaughtExceptionHandler set.
1609      */
1610     @Test
1611     void testUncaughtExceptionHandler3() throws Exception {
1612         class FooException extends RuntimeException { }
1613         Thread thread = Thread.ofVirtual().start(() -> {
1614             throw new FooException();
1615         });
1616         thread.join();
1617         assertNull(thread.getUncaughtExceptionHandler());
1618     }
1619 
1620     /**
1621      * Test Thread::threadId and getId.
1622      */
1623     @Test
1624     void testThreadId1() throws Exception {
1625         record ThreadIds(long threadId, long id) { }
1626         var ref = new AtomicReference<ThreadIds>();
1627 
1628         Thread vthread = Thread.ofVirtual().unstarted(() -> {
1629             Thread thread = Thread.currentThread();
1630             ref.set(new ThreadIds(thread.threadId(), thread.getId()));
1631             LockSupport.park();
1632         });
1633 
1634         // unstarted
1635         long tid = vthread.threadId();
1636 
1637         // running
1638         ThreadIds tids;
1639         vthread.start();
1640         try {
1641             while ((tids = ref.get()) == null) {
1642                 Thread.sleep(10);
1643             }
1644             assertTrue(tids.threadId() == tid);
1645             assertTrue(tids.id() == tid);
1646         } finally {
1647             LockSupport.unpark(vthread);
1648             vthread.join();
1649         }
1650 
1651         // terminated
1652         assertTrue(vthread.threadId() == tid);
1653         assertTrue(vthread.getId() == tid);
1654     }
1655 
1656     /**
1657      * Test that each Thread has a unique ID
1658      */
1659     @Test
1660     void testThreadId2() throws Exception {
1661         // thread ID should be unique
1662         long tid1 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1663         long tid2 = Thread.ofVirtual().unstarted(() -> { }).threadId();
1664         long tid3 = Thread.currentThread().threadId();
1665         assertFalse(tid1 == tid2);
1666         assertFalse(tid1 == tid3);
1667         assertFalse(tid2 == tid3);
1668     }
1669 
1670     /**
1671      * Test Thread::getState when thread is not started.
1672      */
1673     @Test
1674     void testGetState1() {
1675         var thread = Thread.ofVirtual().unstarted(() -> { });
1676         assertTrue(thread.getState() == Thread.State.NEW);
1677     }
1678 
1679     /**
1680      * Test Thread::getState when thread is runnable (mounted).
1681      */
1682     @Test
1683     void testGetState2() throws Exception {
1684         VThreadRunner.run(() -> {
1685             Thread.State state = Thread.currentThread().getState();
1686             assertTrue(state == Thread.State.RUNNABLE);
1687         });
1688     }
1689 
1690     /**
1691      * Test Thread::getState when thread is runnable (not mounted).
1692      */
1693     @Test
1694     void testGetState3() throws Exception {
1695         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1696         AtomicBoolean completed = new AtomicBoolean();
1697         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
1698             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1699             Thread t1 = builder.start(() -> {
1700                 Thread t2 = builder.unstarted(LockSupport::park);
1701                 assertTrue(t2.getState() == Thread.State.NEW);
1702 
1703                 // start t2 to make it runnable
1704                 t2.start();
1705                 try {
1706                     assertTrue(t2.getState() == Thread.State.RUNNABLE);
1707 
1708                     // yield to allow t2 to run and park
1709                     Thread.yield();
1710                     assertTrue(t2.getState() == Thread.State.WAITING);
1711                 } finally {
1712                     // unpark t2 to make it runnable again
1713                     LockSupport.unpark(t2);
1714                 }
1715 
1716                 // t2 should be runnable (not mounted)
1717                 assertTrue(t2.getState() == Thread.State.RUNNABLE);
1718 
1719                 completed.set(true);
1720             });
1721             t1.join();
1722         }
1723         assertTrue(completed.get() == true);
1724     }
1725 
1726     /**
1727      * Test Thread::getState when thread is parked.
1728      */
1729     @Test
1730     void testGetState4() throws Exception {
1731         var thread = Thread.ofVirtual().start(LockSupport::park);
1732         while (thread.getState() != Thread.State.WAITING) {
1733             Thread.sleep(20);
1734         }
1735         LockSupport.unpark(thread);
1736         thread.join();
1737     }
1738 
1739     /**
1740      * Test Thread::getState when thread is parked while holding a monitor.
1741      */
1742     @Test
1743     void testGetState5() throws Exception {
1744         var thread = Thread.ofVirtual().start(() -> {
1745             synchronized (lock) {
1746                 LockSupport.park();
1747             }
1748         });
1749         while (thread.getState() != Thread.State.WAITING) {
1750             Thread.sleep(20);
1751         }
1752         LockSupport.unpark(thread);
1753         thread.join();
1754     }
1755 
1756     /**
1757      * Test Thread::getState when thread is waiting for a monitor.
1758      */
1759     @Test
1760     void testGetState6() throws Exception {
1761         var thread = Thread.ofVirtual().unstarted(() -> {
1762             synchronized (lock) { }
1763         });
1764         synchronized (lock) {
1765             thread.start();
1766             while (thread.getState() != Thread.State.BLOCKED) {
1767                 Thread.sleep(20);
1768             }
1769         }
1770         thread.join();
1771     }
1772 
1773     /**
1774      * Test Thread::getState when thread is waiting in Object.wait.
1775      */
1776     @Test
1777     void testGetState7() throws Exception {
1778         var thread = Thread.ofVirtual().start(() -> {
1779             synchronized (lock) {
1780                 try { lock.wait(); } catch (InterruptedException e) { }
1781             }
1782         });
1783         while (thread.getState() != Thread.State.WAITING) {
1784             Thread.sleep(20);
1785         }
1786         thread.interrupt();
1787         thread.join();
1788     }
1789 
1790     /**
1791      * Test Thread::getState when thread is terminated.
1792      */
1793     @Test
1794     void testGetState8() throws Exception {
1795         var thread = Thread.ofVirtual().start(() -> { });
1796         thread.join();
1797         assertTrue(thread.getState() == Thread.State.TERMINATED);
1798     }
1799 
1800     /**
1801      * Test Thread::isAlive.
1802      */
1803     @Test
1804     void testIsAlive1() throws Exception {
1805         // unstarted
1806         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
1807         assertFalse(thread.isAlive());
1808 
1809         // started
1810         thread.start();
1811         try {
1812             assertTrue(thread.isAlive());
1813         } finally {
1814             LockSupport.unpark(thread);
1815             thread.join();
1816         }
1817 
1818         // terminated
1819         assertFalse(thread.isAlive());
1820     }
1821 
1822     /**
1823      * Test Thread.holdLock when lock not held.
1824      */
1825     @Test
1826     void testHoldsLock1() throws Exception {
1827         VThreadRunner.run(() -> {
1828             var lock = new Object();
1829             assertFalse(Thread.holdsLock(lock));
1830         });
1831     }
1832 
1833     /**
1834      * Test Thread.holdLock when lock held.
1835      */
1836     @Test
1837     void testHoldsLock2() throws Exception {
1838         VThreadRunner.run(() -> {
1839             var lock = new Object();
1840             synchronized (lock) {
1841                 assertTrue(Thread.holdsLock(lock));
1842             }
1843         });
1844     }
1845 
1846     /**
1847      * Test Thread::getStackTrace on unstarted thread.
1848      */
1849     @Test
1850     void testGetStackTrace1() {
1851         var thread = Thread.ofVirtual().unstarted(() -> { });
1852         StackTraceElement[] stack = thread.getStackTrace();
1853         assertTrue(stack.length == 0);
1854     }
1855 
1856     /**
1857      * Test Thread::getStackTrace on thread that has been started but has not run.
1858      */
1859     @Test
1860     void testGetStackTrace2() throws Exception {
1861         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1862         Executor scheduler = task -> { };
1863         Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1864         Thread thread = builder.start(() -> { });
1865         StackTraceElement[] stack = thread.getStackTrace();
1866         assertTrue(stack.length == 0);
1867     }
1868 
1869     /**
1870      * Test Thread::getStackTrace on running thread.
1871      */
1872     @Test
1873     void testGetStackTrace3() throws Exception {
1874         var sel = Selector.open();
1875         var thread = Thread.ofVirtual().start(() -> {
1876             try { sel.select(); } catch (Exception e) { }
1877         });
1878         try {
1879             while (!contains(thread.getStackTrace(), "select")) {
1880                 assertTrue(thread.isAlive());
1881                 Thread.sleep(20);
1882             }
1883         } finally {
1884             sel.close();
1885             thread.join();
1886         }
1887     }
1888 
1889     /**
1890      * Test Thread::getStackTrace on thread waiting in Object.wait.
1891      */
1892     @Test
1893     void testGetStackTrace4() throws Exception {
1894         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1895         try (ForkJoinPool pool = new ForkJoinPool(1)) {
1896             AtomicReference<Thread> ref = new AtomicReference<>();
1897             Executor scheduler = task -> {
1898                 pool.submit(() -> {
1899                     ref.set(Thread.currentThread());
1900                     task.run();
1901                 });
1902             };
1903 
1904             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
1905             Thread vthread = builder.start(() -> {
1906                 synchronized (lock) {
1907                     try {
1908                         lock.wait();
1909                     } catch (Exception e) { }
1910                 }
1911             });
1912 
1913             // get carrier Thread
1914             Thread carrier;
1915             while ((carrier = ref.get()) == null) {
1916                 Thread.sleep(20);
1917             }
1918 
1919             // wait for virtual thread to block in wait
1920             while (vthread.getState() != Thread.State.WAITING) {
1921                 Thread.sleep(20);
1922             }
1923 
1924             // get stack trace of both carrier and virtual thread
1925             StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
1926             StackTraceElement[] vthreadStackTrace = vthread.getStackTrace();
1927 
1928             // allow virtual thread to terminate
1929             synchronized (lock) {
1930                 lock.notifyAll();
1931             }
1932 
1933             // check carrier thread's stack trace
1934             assertTrue(contains(carrierStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
1935             assertFalse(contains(carrierStackTrace, "java.lang.Object.wait"));
1936 
1937             // check virtual thread's stack trace
1938             assertFalse(contains(vthreadStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
1939             assertTrue(contains(vthreadStackTrace, "java.lang.Object.wait"));
1940         }
1941     }
1942 
1943     /**
1944      * Test Thread::getStackTrace on parked thread.
1945      */
1946     @Test
1947     void testGetStackTrace5() throws Exception {
1948         var thread = Thread.ofVirtual().start(LockSupport::park);
1949 
1950         // wait for thread to park
1951         while (thread.getState() != Thread.State.WAITING) {
1952             Thread.sleep(20);
1953         }
1954 
1955         try {
1956             StackTraceElement[] stack = thread.getStackTrace();
1957             assertTrue(contains(stack, "LockSupport.park"));
1958         } finally {
1959             LockSupport.unpark(thread);
1960             thread.join();
1961         }
1962     }
1963 
1964     /**
1965      * Test Thread::getStackTrace on terminated thread.
1966      */
1967     @Test
1968     void testGetStackTrace6() throws Exception {
1969         var thread = Thread.ofVirtual().start(() -> { });
1970         thread.join();
1971         StackTraceElement[] stack = thread.getStackTrace();
1972         assertTrue(stack.length == 0);
1973     }
1974 
1975     /**
1976      * Test that Thread.getAllStackTraces does not include virtual threads.
1977      */
1978     @Test
1979     void testGetAllStackTraces1() throws Exception {
1980         VThreadRunner.run(() -> {
1981             Set<Thread> threads = Thread.getAllStackTraces().keySet();
1982             assertFalse(threads.stream().anyMatch(Thread::isVirtual));
1983         });
1984     }
1985 
1986     /**
1987      * Test that Thread.getAllStackTraces includes carrier threads.
1988      */
1989     @Test
1990     void testGetAllStackTraces2() throws Exception {
1991         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
1992         try (ForkJoinPool pool = new ForkJoinPool(1)) {
1993             AtomicReference<Thread> ref = new AtomicReference<>();
1994             Executor scheduler = task -> {
1995                 pool.submit(() -> {
1996                     ref.set(Thread.currentThread());
1997                     task.run();
1998                 });
1999             };
2000 
2001             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
2002             Thread vthread = builder.start(() -> {
2003                 synchronized (lock) {
2004                     try {
2005                         lock.wait();
2006                     } catch (Exception e) { }
2007                 }
2008             });
2009 
2010             // get carrier Thread
2011             Thread carrier;
2012             while ((carrier = ref.get()) == null) {
2013                 Thread.sleep(20);
2014             }
2015 
2016             // wait for virtual thread to block in wait
2017             while (vthread.getState() != Thread.State.WAITING) {
2018                 Thread.sleep(20);
2019             }
2020 
2021             // get all stack traces
2022             Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
2023 
2024             // allow virtual thread to terminate
2025             synchronized (lock) {
2026                 lock.notifyAll();
2027             }
2028 
2029             // get stack trace for the carrier thread
2030             StackTraceElement[] stackTrace = map.get(carrier);
2031             assertNotNull(stackTrace);
2032             assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
2033             assertFalse(contains(stackTrace, "java.lang.Object.wait"));
2034 
2035             // there should be no stack trace for the virtual thread
2036             assertNull(map.get(vthread));
2037         }
2038     }
2039 
2040     private boolean contains(StackTraceElement[] stack, String expected) {
2041         return Stream.of(stack)
2042                 .map(Object::toString)
2043                 .anyMatch(s -> s.contains(expected));
2044     }
2045 
2046     /**
2047      * Test Thread::getThreadGroup on virtual thread created by platform thread.
2048      */
2049     @Test
2050     void testThreadGroup1() throws Exception {
2051         var thread = Thread.ofVirtual().unstarted(LockSupport::park);
2052         var vgroup = thread.getThreadGroup();
2053         thread.start();
2054         try {
2055             assertTrue(thread.getThreadGroup() == vgroup);
2056         } finally {
2057             LockSupport.unpark(thread);
2058             thread.join();
2059         }
2060         assertNull(thread.getThreadGroup());
2061     }
2062 
2063     /**
2064      * Test Thread::getThreadGroup on platform thread created by virtual thread.
2065      */
2066     @Test
2067     void testThreadGroup2() throws Exception {
2068         VThreadRunner.run(() -> {
2069             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2070             Thread child = new Thread(() -> { });
2071             ThreadGroup group = child.getThreadGroup();
2072             assertTrue(group == vgroup);
2073         });
2074     }
2075 
2076     /**
2077      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2078      * created with 2-arg ThreadGroup constructor.
2079      */
2080     @Test
2081     void testThreadGroup3() throws Exception {
2082         var ref = new AtomicReference<ThreadGroup>();
2083         var thread = Thread.startVirtualThread(() -> {
2084             ref.set(Thread.currentThread().getThreadGroup());
2085         });
2086         thread.join();
2087 
2088         ThreadGroup vgroup = ref.get();
2089         assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2090 
2091         ThreadGroup group = new ThreadGroup(vgroup, "group");
2092         assertTrue(group.getParent() == vgroup);
2093         assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
2094 
2095         vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2096         assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2097         assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
2098 
2099         vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2100         assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2101         assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
2102     }
2103 
2104     /**
2105      * Test ThreadGroup returned by Thread::getThreadGroup and subgroup
2106      * created with 1-arg ThreadGroup constructor.
2107      */
2108     @Test
2109     void testThreadGroup4() throws Exception {
2110         VThreadRunner.run(() -> {
2111             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2112 
2113             assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2114 
2115             ThreadGroup group = new ThreadGroup("group");
2116             assertTrue(group.getParent() == vgroup);
2117             assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY);
2118 
2119             vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
2120             assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2121             assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1);
2122 
2123             vgroup.setMaxPriority(Thread.MIN_PRIORITY);
2124             assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY);
2125             assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY);
2126         });
2127     }
2128 
2129     /**
2130      * Test Thread.enumerate(false).
2131      */
2132     @Test
2133     void testEnumerate1() throws Exception {
2134         VThreadRunner.run(() -> {
2135             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2136             Thread[] threads = new Thread[100];
2137             int n = vgroup.enumerate(threads, /*recurse*/false);
2138             assertTrue(n == 0);
2139         });
2140     }
2141 
2142     /**
2143      * Test Thread.enumerate(true).
2144      */
2145     @Test
2146     void testEnumerate2() throws Exception {
2147         VThreadRunner.run(() -> {
2148             ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
2149             Thread[] threads = new Thread[100];
2150             int n = vgroup.enumerate(threads, /*recurse*/true);
2151             assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
2152         });
2153     }
2154 
2155     /**
2156      * Test equals and hashCode.
2157      */
2158     @Test
2159     void testEqualsAndHashCode() throws Exception {
2160         Thread vthread1 = Thread.ofVirtual().unstarted(LockSupport::park);
2161         Thread vthread2 = Thread.ofVirtual().unstarted(LockSupport::park);
2162 
2163         // unstarted
2164         assertTrue(vthread1.equals(vthread1));
2165         assertTrue(vthread2.equals(vthread2));
2166         assertFalse(vthread1.equals(vthread2));
2167         assertFalse(vthread2.equals(vthread1));
2168         int hc1 = vthread1.hashCode();
2169         int hc2 = vthread2.hashCode();
2170 
2171         vthread1.start();
2172         vthread2.start();
2173         try {
2174             // started, maybe running or parked
2175             assertTrue(vthread1.equals(vthread1));
2176             assertTrue(vthread2.equals(vthread2));
2177             assertFalse(vthread1.equals(vthread2));
2178             assertFalse(vthread2.equals(vthread1));
2179             assertTrue(vthread1.hashCode() == hc1);
2180             assertTrue(vthread2.hashCode() == hc2);
2181         } finally {
2182             LockSupport.unpark(vthread1);
2183             LockSupport.unpark(vthread2);
2184         }
2185         vthread1.join();
2186         vthread2.join();
2187 
2188         // terminated
2189         assertTrue(vthread1.equals(vthread1));
2190         assertTrue(vthread2.equals(vthread2));
2191         assertFalse(vthread1.equals(vthread2));
2192         assertFalse(vthread2.equals(vthread1));
2193         assertTrue(vthread1.hashCode() == hc1);
2194         assertTrue(vthread2.hashCode() == hc2);
2195     }
2196 
2197     /**
2198      * Test toString on unstarted thread.
2199      */
2200     @Test
2201     void testToString1() {
2202         Thread thread = Thread.ofVirtual().unstarted(() -> { });
2203         thread.setName("fred");
2204         assertTrue(thread.toString().contains("fred"));
2205     }
2206 
2207     /**
2208      * Test toString on running thread.
2209      */
2210     @Test
2211     void testToString2() throws Exception {
2212         VThreadRunner.run(() -> {
2213             Thread me = Thread.currentThread();
2214             me.setName("fred");
2215             assertTrue(me.toString().contains("fred"));
2216         });
2217     }
2218 
2219     /**
2220      * Test toString on parked thread.
2221      */
2222     @Test
2223     void testToString3() throws Exception {
2224         Thread thread = Thread.ofVirtual().start(() -> {
2225             Thread me = Thread.currentThread();
2226             me.setName("fred");
2227             LockSupport.park();
2228         });
2229         while (thread.getState() != Thread.State.WAITING) {
2230             Thread.sleep(10);
2231         }
2232         try {
2233             assertTrue(thread.toString().contains("fred"));
2234         } finally {
2235             LockSupport.unpark(thread);
2236             thread.join();
2237         }
2238     }
2239 
2240     /**
2241      * Test toString on terminated thread.
2242      */
2243     @Test
2244     void testToString4() throws Exception {
2245         Thread thread = Thread.ofVirtual().start(() -> {
2246             Thread me = Thread.currentThread();
2247             me.setName("fred");
2248         });
2249         thread.join();
2250         assertTrue(thread.toString().contains("fred"));
2251     }
2252 
2253     /**
2254      * Waits for the given thread to park.
2255      */
2256     static void awaitParked(Thread thread) throws InterruptedException {
2257         Thread.State state = thread.getState();
2258         while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) {
2259             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
2260             Thread.sleep(10);
2261             state = thread.getState();
2262         }
2263     }
2264 
2265     /**
2266      * Waits for the given thread to block waiting on a monitor.
2267      */
2268     static void awaitBlocked(Thread thread) throws InterruptedException {
2269         Thread.State state = thread.getState();
2270         while (state != Thread.State.BLOCKED) {
2271             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
2272             Thread.sleep(10);
2273             state = thread.getState();
2274         }
2275     }
2276 
2277     /**
2278      * Schedule a thread to be interrupted after a delay.
2279      */
2280     private void scheduleInterrupt(Thread thread, long delayInMillis) {
2281         scheduler.schedule(thread::interrupt, delayInMillis, TimeUnit.MILLISECONDS);
2282     }
2283 }