< prev index next >

test/jdk/java/lang/Thread/virtual/ThreadAPI.java

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.
--- 1,7 ---
  /*
!  * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.

*** 59,30 ***
  import java.util.concurrent.locks.LockSupport;
  import java.util.concurrent.locks.ReentrantLock;
  import java.util.stream.Stream;
  import java.nio.channels.Selector;
  
- import jdk.test.lib.thread.VThreadRunner;
  import jdk.test.lib.thread.VThreadPinner;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.api.AfterAll;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.MethodSource;
  import static org.junit.jupiter.api.Assertions.*;
  import static org.junit.jupiter.api.Assumptions.*;
  
  class ThreadAPI {
      private static final Object lock = new Object();
  
      // used for scheduling thread interrupt
      private static ScheduledExecutorService scheduler;
  
      @BeforeAll
!     static void setup() throws Exception {
          ThreadFactory factory = Executors.defaultThreadFactory();
          scheduler = Executors.newSingleThreadScheduledExecutor(factory);
      }
  
      @AfterAll
      static void finish() {
          scheduler.shutdown();
--- 59,36 ---
  import java.util.concurrent.locks.LockSupport;
  import java.util.concurrent.locks.ReentrantLock;
  import java.util.stream.Stream;
  import java.nio.channels.Selector;
  
  import jdk.test.lib.thread.VThreadPinner;
+ import jdk.test.lib.thread.VThreadRunner;
+ import jdk.test.lib.thread.VThreadScheduler;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.api.AfterAll;
+ import org.junit.jupiter.api.Disabled;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.MethodSource;
+ import org.junit.jupiter.params.provider.ValueSource;
  import static org.junit.jupiter.api.Assertions.*;
  import static org.junit.jupiter.api.Assumptions.*;
  
  class ThreadAPI {
      private static final Object lock = new Object();
  
      // used for scheduling thread interrupt
      private static ScheduledExecutorService scheduler;
  
      @BeforeAll
!     static void setup() {
          ThreadFactory factory = Executors.defaultThreadFactory();
          scheduler = Executors.newSingleThreadScheduledExecutor(factory);
+ 
+         // need >=2 carriers for testing pinning
+         VThreadRunner.ensureParallelism(2);
      }
  
      @AfterAll
      static void finish() {
          scheduler.shutdown();

*** 717,18 ***
       * Test virtual thread invoking timed-Thread.join on a thread that is parking
       * and unparking while pinned.
       */
      @Test
      void testJoin34() throws Exception {
!         // need at least two carrier threads due to pinning
-         int previousParallelism = VThreadRunner.ensureParallelism(2);
-         try {
-             VThreadRunner.run(this::testJoin33);
-         } finally {
-             // restore
-             VThreadRunner.setParallelism(previousParallelism);
-         }
      }
  
      /**
       * Test Thread.join(null).
       */
--- 723,11 ---
       * Test virtual thread invoking timed-Thread.join on a thread that is parking
       * and unparking while pinned.
       */
      @Test
      void testJoin34() throws Exception {
!         VThreadRunner.run(this::testJoin33);
      }
  
      /**
       * Test Thread.join(null).
       */

*** 1082,15 ***
      /**
       * Test Thread.yield releases carrier thread.
       */
      @Test
      void testYield1() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
          var list = new CopyOnWriteArrayList<String>();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
-             ThreadFactory factory = builder.factory();
              var thread = factory.newThread(() -> {
                  list.add("A");
                  var child = factory.newThread(() -> {
                      list.add("B");
                      Thread.yield();
--- 1081,14 ---
      /**
       * Test Thread.yield releases carrier thread.
       */
      @Test
      void testYield1() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
          var list = new CopyOnWriteArrayList<String>();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
              var thread = factory.newThread(() -> {
                  list.add("A");
                  var child = factory.newThread(() -> {
                      list.add("B");
                      Thread.yield();

*** 1106,19 ***
          }
          assertEquals(List.of("A", "B", "A", "B"), list);
      }
  
      /**
!      * Test Thread.yield when thread is pinned by native frame.
       */
      @Test
      void testYield2() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
          var list = new CopyOnWriteArrayList<String>();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
!             ThreadFactory factory = builder.factory();
              var thread = factory.newThread(() -> {
                  list.add("A");
                  var child = factory.newThread(() -> {
                      list.add("B");
                  });
--- 1104,48 ---
          }
          assertEquals(List.of("A", "B", "A", "B"), list);
      }
  
      /**
!      * Test Thread.yield releases carrier when virtual thread holds a monitor.
       */
      @Test
      void testYield2() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
          var list = new CopyOnWriteArrayList<String>();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
!             var lock = new Object();
+             var thread = factory.newThread(() -> {
+                 list.add("A");
+                 var child = factory.newThread(() -> {
+                     list.add("B");
+                     synchronized (lock) {
+                         Thread.yield();
+                     }
+                     list.add("B");
+                 });
+                 child.start();
+                 Thread.yield();
+                 list.add("A");
+                 try { child.join(); } catch (InterruptedException e) { }
+             });
+             thread.start();
+             thread.join();
+         }
+         assertEquals(List.of("A", "B", "A", "B"), list);
+     }
+ 
+     /**
+      * Test Thread.yield when thread is pinned by native frame.
+      */
+     @Test
+     void testYield3() throws Exception {
+         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
+         var list = new CopyOnWriteArrayList<String>();
+         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
+             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
              var thread = factory.newThread(() -> {
                  list.add("A");
                  var child = factory.newThread(() -> {
                      list.add("B");
                  });

*** 1137,11 ***
  
      /**
       * Test Thread.yield does not consume the thread's parking permit.
       */
      @Test
!     void testYield3() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              LockSupport.unpark(Thread.currentThread());
              Thread.yield();
              LockSupport.park();  // should not park
          });
--- 1164,11 ---
  
      /**
       * Test Thread.yield does not consume the thread's parking permit.
       */
      @Test
!     void testYield4() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              LockSupport.unpark(Thread.currentThread());
              Thread.yield();
              LockSupport.park();  // should not park
          });

*** 1150,11 ***
  
      /**
       * Test Thread.yield does not make available the thread's parking permit.
       */
      @Test
!     void testYield4() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              Thread.yield();
              LockSupport.park();  // should park
          });
          try {
--- 1177,11 ---
  
      /**
       * Test Thread.yield does not make available the thread's parking permit.
       */
      @Test
!     void testYield5() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              Thread.yield();
              LockSupport.park();  // should park
          });
          try {

*** 1706,95 ***
      /**
       * Test Thread::getState when thread is runnable (not mounted).
       */
      @Test
      void testGetState4() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
          AtomicBoolean completed = new AtomicBoolean();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
!             Thread t1 = builder.start(() -> {
!                 Thread t2 = builder.unstarted(LockSupport::park);
!                 assertEquals(Thread.State.NEW, t2.getState());
  
                  // start t2 to make it runnable
!                 t2.start();
                  try {
!                     assertEquals(Thread.State.RUNNABLE, t2.getState());
  
                      // yield to allow t2 to run and park
                      Thread.yield();
!                     assertEquals(Thread.State.WAITING, t2.getState());
                  } finally {
                      // unpark t2 to make it runnable again
!                     LockSupport.unpark(t2);
                  }
  
                  // t2 should be runnable (not mounted)
!                 assertEquals(Thread.State.RUNNABLE, t2.getState());
  
                  completed.set(true);
              });
!             t1.join();
          }
          assertTrue(completed.get() == true);
      }
  
      /**
!      * Test Thread::getState when thread is waiting to enter a monitor.
       */
!     @Test
!     void testGetState5() throws Exception {
!         var started = new CountDownLatch(1);
          var thread = Thread.ofVirtual().unstarted(() -> {
!             started.countDown();
!             synchronized (lock) { }
          });
          synchronized (lock) {
              thread.start();
!             started.await();
  
              // wait for thread to block
              await(thread, Thread.State.BLOCKED);
          }
          thread.join();
      }
  
      /**
       * Test Thread::getState when thread is waiting in Object.wait.
       */
!     @Test
!     void testGetState6() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              synchronized (lock) {
!                 try { lock.wait(); } catch (InterruptedException e) { }
              }
          });
          try {
              // wait for thread to wait
              await(thread, Thread.State.WAITING);
          } finally {
              thread.interrupt();
              thread.join();
          }
      }
  
      /**
       * Test Thread::getState when thread is waiting in Object.wait(millis).
       */
!     @Test
!     void testGetState7() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
              synchronized (lock) {
                  try {
!                     lock.wait(Long.MAX_VALUE);
                  } catch (InterruptedException e) { }
              }
          });
          try {
!             // wait for thread to wait
              await(thread, Thread.State.TIMED_WAITING);
          } finally {
              thread.interrupt();
              thread.join();
          }
      }
--- 1733,140 ---
      /**
       * Test Thread::getState when thread is runnable (not mounted).
       */
      @Test
      void testGetState4() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
          AtomicBoolean completed = new AtomicBoolean();
          try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
!             Thread thread1 = factory.newThread(() -> {
!                 Thread thread2 = factory.newThread(LockSupport::park);
!                 assertEquals(Thread.State.NEW, thread2.getState());
  
                  // start t2 to make it runnable
!                 thread2.start();
                  try {
!                     assertEquals(Thread.State.RUNNABLE, thread2.getState());
  
                      // yield to allow t2 to run and park
                      Thread.yield();
!                     assertEquals(Thread.State.WAITING, thread2.getState());
                  } finally {
                      // unpark t2 to make it runnable again
!                     LockSupport.unpark(thread2);
                  }
  
                  // t2 should be runnable (not mounted)
!                 assertEquals(Thread.State.RUNNABLE, thread2.getState());
  
                  completed.set(true);
              });
!             thread1.start();
+             thread1.join();
          }
          assertTrue(completed.get() == true);
      }
  
      /**
!      * Test Thread::getState when thread is blocked waiting to enter a monitor.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
!     void testGetState5(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
          var thread = Thread.ofVirtual().unstarted(() -> {
!             if (pinned) {
!                 VThreadPinner.runPinned(() -> {
+                     ready.set(true);
+                     synchronized (lock) { }
+                 });
+             } else {
+                 ready.set(true);
+                 synchronized (lock) { }
+             }
          });
          synchronized (lock) {
              thread.start();
!             awaitTrue(ready);
  
              // wait for thread to block
              await(thread, Thread.State.BLOCKED);
          }
          thread.join();
      }
  
      /**
       * Test Thread::getState when thread is waiting in Object.wait.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
+     void testGetState6(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
              synchronized (lock) {
!                 try {
+                     if (pinned) {
+                         VThreadPinner.runPinned(() -> {
+                             ready.set(true);
+                             lock.wait();
+                         });
+                     } else {
+                         ready.set(true);
+                         lock.wait();
+                     }
+                 } catch (InterruptedException e) { }
              }
          });
          try {
              // wait for thread to wait
+             awaitTrue(ready);
              await(thread, Thread.State.WAITING);
+ 
+             // notify, thread should block trying to reenter
+             synchronized (lock) {
+                 lock.notifyAll();
+                 await(thread, Thread.State.BLOCKED);
+             }
          } finally {
              thread.interrupt();
              thread.join();
          }
      }
  
      /**
       * Test Thread::getState when thread is waiting in Object.wait(millis).
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
+     void testGetState7(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
              synchronized (lock) {
                  try {
!                     if (pinned) {
+                         VThreadPinner.runPinned(() -> {
+                             ready.set(true);
+                             lock.wait(Long.MAX_VALUE);
+                         });
+                     } else {
+                         ready.set(true);
+                         lock.wait(Long.MAX_VALUE);
+                     }
                  } catch (InterruptedException e) { }
              }
          });
          try {
!             // wait for thread to timed-wait
+             awaitTrue(ready);
              await(thread, Thread.State.TIMED_WAITING);
+ 
+             // notify, thread should block trying to reenter
+             synchronized (lock) {
+                 lock.notifyAll();
+                 await(thread, Thread.State.BLOCKED);
+             }
          } finally {
              thread.interrupt();
              thread.join();
          }
      }

*** 1927,178 ***
                  assertTrue(Thread.holdsLock(lock));
              }
          });
      }
  
      /**
       * Test Thread::getStackTrace on unstarted thread.
       */
      @Test
!     void testGetStackTrace1() {
          var thread = Thread.ofVirtual().unstarted(() -> { });
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }
  
      /**
       * Test Thread::getStackTrace on thread that has been started but has not run.
       */
      @Test
!     void testGetStackTrace2() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
          Executor scheduler = task -> { };
!         Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
!         Thread thread = builder.start(() -> { });
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }
  
      /**
!      * Test Thread::getStackTrace on running thread.
       */
      @Test
!     void testGetStackTrace3() throws Exception {
!         var sel = Selector.open();
!         var thread = Thread.ofVirtual().start(() -> {
!             try { sel.select(); } catch (Exception e) { }
!         });
!         try {
!             while (!contains(thread.getStackTrace(), "select")) {
!                 assertTrue(thread.isAlive());
!                 Thread.sleep(20);
              }
          } finally {
!             sel.close();
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace on thread waiting in Object.wait.
       */
      @Test
!     void testGetStackTrace4() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
-         try (ForkJoinPool pool = new ForkJoinPool(1)) {
-             AtomicReference<Thread> ref = new AtomicReference<>();
-             Executor scheduler = task -> {
-                 pool.submit(() -> {
-                     ref.set(Thread.currentThread());
-                     task.run();
-                 });
-             };
  
!             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
!             Thread vthread = builder.start(() -> {
!                 synchronized (lock) {
!                     try {
!                         lock.wait();
!                     } catch (Exception e) { }
                  }
              });
  
!             // get carrier Thread
!             Thread carrier;
!             while ((carrier = ref.get()) == null) {
!                 Thread.sleep(20);
!             }
  
!             // wait for virtual thread to block in wait
!             await(vthread, Thread.State.WAITING);
  
!             // get stack trace of both carrier and virtual thread
!             StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
!             StackTraceElement[] vthreadStackTrace = vthread.getStackTrace();
  
!             // allow virtual thread to terminate
!             synchronized (lock) {
!                 lock.notifyAll();
              }
  
!             // check carrier thread's stack trace
!             assertTrue(contains(carrierStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
-             assertFalse(contains(carrierStackTrace, "java.lang.Object.wait"));
  
!             // check virtual thread's stack trace
!             assertFalse(contains(vthreadStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
-             assertTrue(contains(vthreadStackTrace, "java.lang.Object.wait"));
          }
      }
  
      /**
!      * Test Thread::getStackTrace on parked thread.
       */
!     @Test
!     void testGetStackTrace5() throws Exception {
!         var thread = Thread.ofVirtual().start(LockSupport::park);
!         await(thread, Thread.State.WAITING);
          try {
              StackTraceElement[] stack = thread.getStackTrace();
!             assertTrue(contains(stack, "LockSupport.park"));
          } finally {
!             LockSupport.unpark(thread);
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace on timed-parked thread.
       */
!     @Test
!     void testGetStackTrace6() throws Exception {
          var thread = Thread.ofVirtual().start(() -> {
!             LockSupport.parkNanos(Long.MAX_VALUE);
          });
-         await(thread, Thread.State.TIMED_WAITING);
          try {
              StackTraceElement[] stack = thread.getStackTrace();
!             assertTrue(contains(stack, "LockSupport.parkNanos"));
          } finally {
!             LockSupport.unpark(thread);
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace on parked thread that is pinned.
       */
!     @Test
!     void testGetStackTrace7() throws Exception {
!         AtomicBoolean done = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
!             VThreadPinner.runPinned(() -> {
                  while (!done.get()) {
                      LockSupport.park();
                  }
!             });
          });
-         await(thread, Thread.State.WAITING);
          try {
              StackTraceElement[] stack = thread.getStackTrace();
              assertTrue(contains(stack, "LockSupport.park"));
          } finally {
              done.set(true);
              LockSupport.unpark(thread);
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace on timed-parked thread that is pinned.
       */
!     @Test
!     void testGetStackTrace8() throws Exception {
!         AtomicBoolean done = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
!             VThreadPinner.runPinned(() -> {
                  while (!done.get()) {
                      LockSupport.parkNanos(Long.MAX_VALUE);
                  }
!             });
          });
-         await(thread, Thread.State.TIMED_WAITING);
          try {
              StackTraceElement[] stack = thread.getStackTrace();
              assertTrue(contains(stack, "LockSupport.parkNanos"));
          } finally {
              done.set(true);
              LockSupport.unpark(thread);
--- 1999,311 ---
                  assertTrue(Thread.holdsLock(lock));
              }
          });
      }
  
+     /**
+      * Test Thread.holdsLock when lock held by carrier thread.
+      */
+     @Disabled
+     @Test
+     void testHoldsLock3() throws Exception {
+         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
+ 
+         Object lock = new Object();
+ 
+         // carrier thread runs all tasks while holding the lock
+         ThreadFactory carrierThreadFactory = task -> Thread.ofPlatform().unstarted(() -> {
+             synchronized (lock) {
+                 task.run();
+             }
+         });
+         try (ExecutorService pool = Executors.newSingleThreadExecutor(carrierThreadFactory)) {
+             Executor scheduler = task -> pool.submit(task::run);
+             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
+ 
+             // start virtual that tests if it holds the lock
+             var result = new AtomicReference<Boolean>();
+             Thread vthread = factory.newThread(() -> {
+                 result.set(Thread.holdsLock(lock));
+             });
+             vthread.start();
+             vthread.join();
+             boolean holdsLock = result.get();
+             assertFalse(holdsLock, "Thread.holdsLock should return false");
+         }
+     }
+ 
      /**
       * Test Thread::getStackTrace on unstarted thread.
       */
      @Test
!     void testGetStackTraceUnstarted() {
          var thread = Thread.ofVirtual().unstarted(() -> { });
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }
  
      /**
       * Test Thread::getStackTrace on thread that has been started but has not run.
       */
      @Test
!     void testGetStackTraceStarted() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
          Executor scheduler = task -> { };
!         ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
!         Thread thread = factory.newThread(() -> { });
+         thread.start();
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }
  
      /**
!      * Test Thread::getStackTrace on thread that is runnable-mounted.
       */
      @Test
!     void testGetStackTraceRunnableMounted() throws Exception {
!         var ready = new AtomicBoolean();
!         var done = new AtomicBoolean();
! 
!         class Foo {
!             void spinUntilDone() {
!                 ready.set(true);
!                 while (!done.get()) {
!                     Thread.onSpinWait();
+                 }
              }
+         }
+ 
+         Foo foo = new Foo();
+         var thread = Thread.ofVirtual().start(foo::spinUntilDone);
+         try {
+             awaitTrue(ready);
+             StackTraceElement[] stack = thread.getStackTrace();
+             assertTrue(contains(stack, Foo.class.getName() + ".spinUntilDone"));
          } finally {
!             done.set(true);
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace on thread that is runnable-unmounted.
       */
      @Test
!     void testGetStackTraceRunnableUnmounted() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
  
!         // custom scheduler with one carrier thread
!         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
!             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
! 
!             // start thread1 to park
!             Thread thread1 = factory.newThread(LockSupport::park);
+             thread1.start();
+             await(thread1, Thread.State.WAITING);
+ 
+             // start thread2 to spin and pin the carrier thread
+             var started = new AtomicBoolean();
+             var done = new AtomicBoolean();
+             Thread thread2 = factory.newThread(() -> {
+                 started.set(true);
+                 while (!done.get()) {
+                     Thread.onSpinWait();
                  }
              });
+             thread2.start();
+             awaitTrue(started);
  
!             try {
!                 // unpark thread1, it should be "stuck" in runnable state
!                 // (the carrier thread is pinned, no other virtual thread can run)
!                 LockSupport.unpark(thread1);
!                 assertEquals(Thread.State.RUNNABLE, thread1.getState());
  
!                 // print thread1's stack trace
!                 StackTraceElement[] stack = thread1.getStackTrace();
+                 assertTrue(contains(stack, "LockSupport.park"));
  
!             } finally {
!                 done.set(true);
!             }
+         }
+     }
  
!     /**
!      * Test Thread::getStackTrace on thread blocked on monitor enter.
!      */
+     @ParameterizedTest
+     @ValueSource(booleans = { true, false })
+     void testGetStackTraceBlocked(boolean pinned) throws Exception {
+         class Foo {
+             void enter() {
+                 synchronized (this) { }
              }
+         }
+         Foo foo = new Foo();
+         var ready = new AtomicBoolean();
+         var thread = Thread.ofVirtual().unstarted(() -> {
+             if (pinned) {
+                 VThreadPinner.runPinned(() -> {
+                     ready.set(true);
+                     foo.enter();
+                 });
+             } else {
+                 ready.set(true);
+                 foo.enter();
+             }
+         });
+         synchronized (foo) {
+             thread.start();
+             awaitTrue(ready);
  
!             // wait for thread to block
!             await(thread, Thread.State.BLOCKED);
  
!             StackTraceElement[] stack = thread.getStackTrace();
!             assertTrue(contains(stack, Foo.class.getName() + ".enter"));
          }
+         thread.join();
      }
  
      /**
!      * Test Thread::getStackTrace when thread is waiting in Object.wait.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
!     void testGetStackTraceWaiting(boolean pinned) throws Exception {
!         var ready = new AtomicBoolean();
+         var thread = Thread.ofVirtual().start(() -> {
+             synchronized (lock) {
+                 try {
+                     if (pinned) {
+                         VThreadPinner.runPinned(() -> {
+                             ready.set(true);
+                             lock.wait();
+                         });
+                     } else {
+                         ready.set(true);
+                         lock.wait();
+                     }
+                 } catch (InterruptedException e) { }
+             }
+         });
          try {
+             // wait for thread to wait
+             awaitTrue(ready);
+             await(thread, Thread.State.WAITING);
+ 
              StackTraceElement[] stack = thread.getStackTrace();
!             assertTrue(contains(stack, "Object.wait"));
          } finally {
!             thread.interrupt();
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace when thread is waiting in timed-Object.wait.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
+     void testGetStackTraceTimedWaiting(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
!             synchronized (lock) {
+                 try {
+                     if (pinned) {
+                         VThreadPinner.runPinned(() -> {
+                             ready.set(true);
+                             lock.wait(Long.MAX_VALUE);
+                         });
+                     } else {
+                         ready.set(true);
+                         lock.wait(Long.MAX_VALUE);
+                     }
+                 } catch (InterruptedException e) { }
+             }
          });
          try {
+             // wait for thread to wait
+             awaitTrue(ready);
+             await(thread, Thread.State.TIMED_WAITING);
+ 
              StackTraceElement[] stack = thread.getStackTrace();
!             assertTrue(contains(stack, "Object.wait"));
          } finally {
!             thread.interrupt();
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace when thread in park.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
!     void testGetStackTraceParked(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
+         var done = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
!             if (pinned) {
+                 VThreadPinner.runPinned(() -> {
+                     ready.set(true);
+                     while (!done.get()) {
+                         LockSupport.park();
+                     }
+                 });
+             } else {
+                 ready.set(true);
                  while (!done.get()) {
                      LockSupport.park();
                  }
!             }
          });
          try {
+             // wait for thread to park
+             awaitTrue(ready);
+             await(thread, Thread.State.WAITING);
+ 
              StackTraceElement[] stack = thread.getStackTrace();
              assertTrue(contains(stack, "LockSupport.park"));
          } finally {
              done.set(true);
              LockSupport.unpark(thread);
              thread.join();
          }
      }
  
      /**
!      * Test Thread::getStackTrace when thread in timed-park.
       */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
!     void testGetStackTraceTimedPark(boolean pinned) throws Exception {
+         var ready = new AtomicBoolean();
+         var done = new AtomicBoolean();
          var thread = Thread.ofVirtual().start(() -> {
!             if (pinned) {
+                 ready.set(true);
+                 VThreadPinner.runPinned(() -> {
+                     while (!done.get()) {
+                         LockSupport.parkNanos(Long.MAX_VALUE);
+                     }
+                 });
+             } else {
+                 ready.set(true);
                  while (!done.get()) {
                      LockSupport.parkNanos(Long.MAX_VALUE);
                  }
!             }
          });
          try {
+             // wait for thread to park
+             awaitTrue(ready);
+             await(thread, Thread.State.TIMED_WAITING);
+ 
              StackTraceElement[] stack = thread.getStackTrace();
              assertTrue(contains(stack, "LockSupport.parkNanos"));
          } finally {
              done.set(true);
              LockSupport.unpark(thread);

*** 2108,11 ***
  
      /**
       * Test Thread::getStackTrace on terminated thread.
       */
      @Test
!     void testGetStackTrace9() throws Exception {
          var thread = Thread.ofVirtual().start(() -> { });
          thread.join();
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }
--- 2313,11 ---
  
      /**
       * Test Thread::getStackTrace on terminated thread.
       */
      @Test
!     void testGetStackTraceTerminated() throws Exception {
          var thread = Thread.ofVirtual().start(() -> { });
          thread.join();
          StackTraceElement[] stack = thread.getStackTrace();
          assertTrue(stack.length == 0);
      }

*** 2131,28 ***
      /**
       * Test that Thread.getAllStackTraces includes carrier threads.
       */
      @Test
      void testGetAllStackTraces2() throws Exception {
!         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
          try (ForkJoinPool pool = new ForkJoinPool(1)) {
              AtomicReference<Thread> ref = new AtomicReference<>();
              Executor scheduler = task -> {
                  pool.submit(() -> {
                      ref.set(Thread.currentThread());
                      task.run();
                  });
              };
  
!             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
!             Thread vthread = builder.start(() -> {
                  synchronized (lock) {
                      try {
                          lock.wait();
                      } catch (Exception e) { }
                  }
              });
  
              // get carrier Thread
              Thread carrier;
              while ((carrier = ref.get()) == null) {
                  Thread.sleep(20);
--- 2336,29 ---
      /**
       * Test that Thread.getAllStackTraces includes carrier threads.
       */
      @Test
      void testGetAllStackTraces2() throws Exception {
!         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
          try (ForkJoinPool pool = new ForkJoinPool(1)) {
              AtomicReference<Thread> ref = new AtomicReference<>();
              Executor scheduler = task -> {
                  pool.submit(() -> {
                      ref.set(Thread.currentThread());
                      task.run();
                  });
              };
  
!             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
!             Thread vthread = factory.newThread(() -> {
                  synchronized (lock) {
                      try {
                          lock.wait();
                      } catch (Exception e) { }
                  }
              });
+             vthread.start();
  
              // get carrier Thread
              Thread carrier;
              while ((carrier = ref.get()) == null) {
                  Thread.sleep(20);

*** 2166,12 ***
  
              // allow virtual thread to terminate
              synchronized (lock) {
                  lock.notifyAll();
              }
  
!             // get stack trace for the carrier thread
              StackTraceElement[] stackTrace = map.get(carrier);
              assertNotNull(stackTrace);
              assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
              assertFalse(contains(stackTrace, "java.lang.Object.wait"));
  
--- 2372,13 ---
  
              // allow virtual thread to terminate
              synchronized (lock) {
                  lock.notifyAll();
              }
+             vthread.join();
  
!             // stack trace for the carrier thread
              StackTraceElement[] stackTrace = map.get(carrier);
              assertNotNull(stackTrace);
              assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
              assertFalse(contains(stackTrace, "java.lang.Object.wait"));
  

*** 2415,10 ***
--- 2622,19 ---
                  return exception;
              }
          }
      }
  
+     /**
+      * Waits for the boolean value to become true.
+      */
+     private static void awaitTrue(AtomicBoolean ref) throws Exception {
+         while (!ref.get()) {
+             Thread.sleep(20);
+         }
+     }
+ 
      /**
       * Waits for the given thread to reach a given state.
       */
      private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
          Thread.State state = thread.getState();
< prev index next >