< prev index next >

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

Print this page
@@ -22,15 +22,95 @@
   */
  
  /*
   * @test id=default
   * @summary Test virtual threads using Object.wait/notifyAll
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
   * @modules java.base/java.lang:+open
   * @library /test/lib
   * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorWaitNotify
   */
  
+ /*
+  * @test id=LM_LEGACY
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=LM_LIGHTWEIGHT
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xint-LM_LEGACY
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xint-LM_LIGHTWEIGHT
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-LM_LEGACY
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-LM_LIGHTWEIGHT
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-noTieredCompilation-LM_LEGACY
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
+ /*
+  * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT
+  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
+  * @modules java.base/java.lang:+open
+  * @library /test/lib
+  * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
+  */
+ 
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.Executors;

@@ -49,11 +129,13 @@
  import jdk.test.lib.thread.VThreadRunner;
  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.params.provider.Arguments;
  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 MonitorWaitNotify {

@@ -202,10 +284,131 @@
                  lock.notifyAll();
              }
              thread.join();
          }
      }
+ 
+     /**
+      * Returns a stream of elements that are ordered pairs of platform and virtual thread
+      * counts. 0,2,4,..8 platform threads. 2,4,6,..16 virtual threads.
+      */
+     static Stream<Arguments> threadCounts() {
+         return IntStream.range(0, 9)
+                 .filter(i -> i % 2 == 0)
+                 .mapToObj(i -> i)
+                 .flatMap(np -> IntStream.range(2, 17)
+                         .filter(i -> i % 2 == 0)
+                         .mapToObj(vp -> Arguments.of(np, vp)));
+     }
+ 
+     /**
+      * Test notify wakes only one thread when platform and virtual threads are waiting.
+      */
+     @ParameterizedTest
+     @MethodSource("threadCounts")
+     void testNotifyOneThread(int nPlatformThreads, int nVirtualThreads) throws Exception {
+         int nThreads = nPlatformThreads + nVirtualThreads;
+ 
+         var lock = new Object();
+         var ready = new CountDownLatch(nThreads);
+         var notified = new AtomicInteger();
+ 
+         Runnable waitTask = () -> {
+             synchronized (lock) {
+                 try {
+                     ready.countDown();
+                     lock.wait();
+                     notified.incrementAndGet();
+                 } catch (InterruptedException e) { }
+             }
+         };
+ 
+         var threads = new ArrayList<Thread>();
+         try {
+             for (int i = 0; i < nPlatformThreads; i++) {
+                 threads.add(Thread.ofPlatform().start(waitTask));
+             }
+             for (int i = 0; i < nVirtualThreads; i++) {
+                 threads.add(Thread.ofVirtual().start(waitTask));
+             }
+ 
+             // wait for all threads to wait
+             ready.await();
+ 
+             // wake threads, one by one
+             for (int i = 0; i < threads.size(); i++) {
+ 
+                 // wake one thread
+                 synchronized (lock) {
+                     lock.notify();
+                 }
+ 
+                 // one thread should have awoken
+                 int expectedWakeups = i + 1;
+                 while (notified.get() < expectedWakeups) {
+                     Thread.sleep(10);
+                 }
+                 assertEquals(expectedWakeups, notified.get());
+             }
+         } finally {
+             for (Thread t : threads) {
+                 t.interrupt();
+                 t.join();
+             }
+         }
+     }
+ 
+     /**
+      * Test notifyAll wakes all threads.
+      */
+     @ParameterizedTest
+     @MethodSource("threadCounts")
+     void testNotifyAllThreads(int nPlatformThreads, int nVirtualThreads) throws Exception {
+         int nThreads = nPlatformThreads + nVirtualThreads;
+ 
+         var lock = new Object();
+         var ready = new CountDownLatch(nThreads);
+         var notified = new CountDownLatch(nThreads);
+ 
+         Runnable waitTask = () -> {
+             synchronized (lock) {
+                 try {
+                     ready.countDown();
+                     lock.wait();
+                     notified.countDown();
+                 } catch (InterruptedException e) { }
+             }
+         };
+ 
+         var threads = new ArrayList<Thread>();
+         try {
+             for (int i = 0; i < nPlatformThreads; i++) {
+                 threads.add(Thread.ofPlatform().start(waitTask));
+             }
+             for (int i = 0; i < nVirtualThreads; i++) {
+                 threads.add(Thread.ofVirtual().start(waitTask));
+             }
+ 
+             // wait for all threads to wait
+             ready.await();
+ 
+             // wakeup all threads
+             synchronized (lock) {
+                 lock.notifyAll();
+             }
+ 
+             // wait for all threads to have awoken
+             notified.await();
+ 
+         } finally {
+             for (Thread t : threads) {
+                 t.interrupt();
+                 t.join();
+             }
+         }
+     }
+ 
      /**
       * Test duration of timed Object.wait.
       */
      @Test
      void testTimedWaitDuration1() throws Exception {

@@ -488,10 +691,99 @@
          // thread should terminate
          vthread.join();
          assertTrue(completed.get());
      }
  
+     /**
+      * Test that Object.wait releases the carrier. This test uses a custom scheduler
+      * with one carrier thread.
+      */
+     @ParameterizedTest
+     @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
+     void testReleaseWhenWaiting1(int timeout) throws Exception {
+         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
+         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
+             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
+ 
+             var lock = new Object();
+             var ready = new AtomicBoolean();
+             var completed = new AtomicBoolean();
+ 
+             var vthread1 = factory.newThread(() -> {
+                 synchronized (lock) {
+                     try {
+                         ready.set(true);
+                         if (timeout > 0) {
+                             lock.wait(timeout);
+                         } else {
+                             lock.wait();
+                         }
+                     } catch (InterruptedException e) {
+                         fail("wait interrupted");
+                     }
+                 }
+                 completed.set(true);
+             });
+             vthread1.start();
+ 
+             // wait for vthread1 to start and wait
+             awaitTrue(ready);
+             await(vthread1, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
+ 
+             // carrier should be released, use it for another thread
+             var executed = new AtomicBoolean();
+             var vthread2 = factory.newThread(() -> {
+                 executed.set(true);
+             });
+             vthread2.start();
+             vthread2.join();
+             assertTrue(executed.get());
+ 
+             // wakeup vthread1
+             synchronized (lock) {
+                 lock.notifyAll();
+             }
+ 
+             vthread1.join();
+             assertTrue(completed.get());
+         }
+     }
+ 
+     /**
+      * Test that Object.wait releases the carrier. This test arranges for 4*ncores - 1
+      * virtual threads to wait. For long timeout and no timeout cases, all virtual threads
+      * will wait until they are notified.
+      */
+     @ParameterizedTest
+     @ValueSource(ints = { 0, 10, 20, 100, 500, 30000, Integer.MAX_VALUE })
+     void testReleaseWhenWaiting2(int timeout) throws Exception {
+         int VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
+         CountDownLatch latch = new CountDownLatch(VTHREAD_COUNT);
+         Object lock = new Object();
+         AtomicInteger counter = new AtomicInteger(0);
+ 
+         for (int i = 0; i < VTHREAD_COUNT; i++) {
+             Thread.ofVirtual().name("vthread-" + i).start(() -> {
+                 synchronized (lock) {
+                     if (counter.incrementAndGet() == VTHREAD_COUNT) {
+                         lock.notifyAll();
+                     } else {
+                         try {
+                             if (timeout > 0) {
+                                 lock.wait(timeout);
+                             } else {
+                                 lock.wait();
+                             }
+                         } catch (InterruptedException e) {}
+                     }
+                 }
+                 latch.countDown();
+             });
+         }
+         latch.await();
+     }
+ 
      /**
       * Test that wait(long) throws IAE when timeout is negative.
       */
      @Test
      void testIllegalArgumentException() throws Exception {
< prev index next >