< prev index next >

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

Print this page
@@ -1,7 +1,7 @@
  /*
-  * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
+  * Copyright (c) 2021, 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.

@@ -38,31 +38,38 @@
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.RejectedExecutionException;
  import java.util.concurrent.ThreadFactory;
  import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.concurrent.atomic.AtomicReference;
  import java.util.concurrent.locks.LockSupport;
- import java.util.function.Consumer;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import jdk.jfr.EventType;
  import jdk.jfr.Recording;
  import jdk.jfr.consumer.RecordedEvent;
  import jdk.jfr.consumer.RecordingFile;
  
+ import jdk.test.lib.thread.VThreadRunner;
  import jdk.test.lib.thread.VThreadPinner;
- import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
  import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.params.ParameterizedTest;
- import org.junit.jupiter.params.provider.Arguments;
- import org.junit.jupiter.params.provider.MethodSource;
+ import org.junit.jupiter.params.provider.ValueSource;
  import static org.junit.jupiter.api.Assertions.*;
  
  class JfrEvents {
  
+     @BeforeAll
+     static void setup() {
+         int minParallelism = 2;
+         if (Thread.currentThread().isVirtual()) {
+             minParallelism++;
+         }
+         VThreadRunner.ensureParallelism(minParallelism);
+     }
+ 
      /**
       * Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
       */
      @Test
      void testVirtualThreadStartAndEnd() throws Exception {

@@ -91,104 +98,263 @@
              assertEquals(100, endCount);
          }
      }
  
      /**
-      * Arguments for testVirtualThreadPinned to test jdk.VirtualThreadPinned event.
-      *   [0] label/description
-      *   [1] the operation to park/wait
-      *   [2] the Thread.State when parked/waiting
-      *   [3] the action to unpark/notify the thread
+      * Test jdk.VirtualThreadPinned event when parking while pinned.
       */
-     static Stream<Arguments> pinnedCases() {
-         Object lock = new Object();
- 
-         // park with native frame on stack
-         var finish1 = new AtomicBoolean();
-         var parkWhenPinned = Arguments.of(
-             "LockSupport.park when pinned",
-             (ThrowingRunnable<Exception>) () -> {
+     @ParameterizedTest
+     @ValueSource(booleans = { true, false })
+     void testParkWhenPinned(boolean timed) throws Exception {
+         try (Recording recording = new Recording()) {
+             recording.enable("jdk.VirtualThreadPinned");
+             recording.start();
+ 
+             var started = new AtomicBoolean();
+             var done = new AtomicBoolean();
+             var vthread = Thread.startVirtualThread(() -> {
                  VThreadPinner.runPinned(() -> {
-                     while (!finish1.get()) {
-                         LockSupport.park();
+                     started.set(true);
+                     while (!done.get()) {
+                         if (timed) {
+                             LockSupport.parkNanos(Long.MAX_VALUE);
+                         } else {
+                             LockSupport.park();
+                         }
                      }
                  });
-             },
-             Thread.State.WAITING,
-                 (Consumer<Thread>) t -> {
-                     finish1.set(true);
-                     LockSupport.unpark(t);
+             });
+ 
+             try {
+                 // wait for thread to start and park
+                 awaitTrue(started);
+                 await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
+             } finally {
+                 done.set(true);
+                 LockSupport.unpark(vthread);
+                 vthread.join();
+                 recording.stop();
+             }
+ 
+             assertContainsPinnedEvent(recording, vthread);
+         }
+     }
+ 
+     /**
+      * Test jdk.VirtualThreadPinned event when blocking on monitor while pinned.
+      */
+     @Test
+     void testBlockWhenPinned() throws Exception {
+         try (Recording recording = new Recording()) {
+             recording.enable("jdk.VirtualThreadPinned");
+             recording.start();
+ 
+             Object lock = new Object();
+ 
+             var started = new AtomicBoolean();
+             var vthread = Thread.ofVirtual().unstarted(() -> {
+                 VThreadPinner.runPinned(() -> {
+                     started.set(true);
+                     synchronized (lock) { }
+                 });
+             });
+ 
+             try {
+                 synchronized (lock) {
+                     vthread.start();
+                     // wait for thread to start and block
+                     awaitTrue(started);
+                     await(vthread, Thread.State.BLOCKED);
                  }
-         );
+             } finally {
+                 vthread.join();
+                 recording.stop();
+             }
  
-         // timed park with native frame on stack
-         var finish2 = new AtomicBoolean();
-         var timedParkWhenPinned = Arguments.of(
-             "LockSupport.parkNanos when pinned",
-             (ThrowingRunnable<Exception>) () -> {
+             assertContainsPinnedEvent(recording, vthread);
+         }
+     }
+ 
+     /**
+      * Test jdk.VirtualThreadPinned event when waiting with Object.wait while pinned.
+      */
+     @ParameterizedTest
+     @ValueSource(booleans = { true, false })
+     void testObjectWaitWhenPinned(boolean timed) throws Exception {
+         try (Recording recording = new Recording()) {
+             recording.enable("jdk.VirtualThreadPinned");
+             recording.start();
+ 
+             Object lock = new Object();
+ 
+             var started = new AtomicBoolean();
+             var vthread = Thread.startVirtualThread(() -> {
                  VThreadPinner.runPinned(() -> {
-                     while (!finish2.get()) {
-                         LockSupport.parkNanos(Long.MAX_VALUE);
+                     started.set(true);
+                     synchronized (lock) {
+                         try {
+                             if (timed) {
+                                 lock.wait(Long.MAX_VALUE);
+                             } else {
+                                 lock.wait();
+                             }
+                         } catch (InterruptedException e) {
+                             fail();
+                         }
                      }
                  });
-             },
-             Thread.State.TIMED_WAITING,
-             (Consumer<Thread>) t -> {
-                 finish2.set(true);
-                 LockSupport.unpark(t);
+             });
+ 
+             try {
+                 // wait for thread to start and wait
+                 awaitTrue(started);
+                 await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
+             } finally {
+                 synchronized (lock) {
+                     lock.notifyAll();
+                 }
+                 vthread.join();
+                 recording.stop();
              }
-         );
  
-         return Stream.of(parkWhenPinned, timedParkWhenPinned);
+             assertContainsPinnedEvent(recording, vthread);
+         }
      }
  
      /**
-      * Test jdk.VirtualThreadPinned event.
+      * Test jdk.VirtualThreadPinned event when parking in a class initializer.
       */
-     @ParameterizedTest
-     @MethodSource("pinnedCases")
-     void testVirtualThreadPinned(String label,
-                                  ThrowingRunnable<Exception> parker,
-                                  Thread.State expectedState,
-                                  Consumer<Thread> unparker) throws Exception {
+     @Test
+     void testParkInClassInitializer() throws Exception {
+         class TestClass {
+             static {
+                 LockSupport.park();
+             }
+             static void m() {
+                 // do nothing
+             }
+         }
  
          try (Recording recording = new Recording()) {
              recording.enable("jdk.VirtualThreadPinned");
+             recording.start();
+ 
+             var started = new AtomicBoolean();
+             Thread vthread = Thread.startVirtualThread(() -> {
+                 started.set(true);
+                 TestClass.m();
+             });
+ 
+             try {
+                 // wait for it to start and park
+                 awaitTrue(started);
+                 await(vthread, Thread.State.WAITING);
+             } finally {
+                 LockSupport.unpark(vthread);
+                 vthread.join();
+                 recording.stop();
+             }
  
+             assertContainsPinnedEvent(recording, vthread);
+         }
+     }
+ 
+     /**
+      * Test jdk.VirtualThreadPinned event when blocking on monitor in a class initializer.
+      */
+     @Test
+     void testBlockInClassInitializer() throws Exception {
+         class LockHolder {
+             static final Object lock = new Object();
+         }
+         class TestClass {
+             static {
+                 synchronized (LockHolder.lock) { }
+             }
+             static void m() {
+                 // no nothing
+             }
+         }
+ 
+         try (Recording recording = new Recording()) {
+             recording.enable("jdk.VirtualThreadPinned");
              recording.start();
+ 
+             var started = new AtomicBoolean();
+             Thread vthread = Thread.ofVirtual().unstarted(() -> {
+                 started.set(true);
+                 TestClass.m();
+             });
+ 
              try {
-                 var exception = new AtomicReference<Throwable>();
-                 var thread = Thread.ofVirtual().start(() -> {
-                     try {
-                         parker.run();
-                     } catch (Throwable e) {
-                         exception.set(e);
-                     }
-                 });
-                 try {
-                     // wait for thread to park/wait
-                     Thread.State state = thread.getState();
-                     while (state != expectedState) {
-                         assertTrue(state != Thread.State.TERMINATED, thread.toString());
-                         Thread.sleep(10);
-                         state = thread.getState();
-                     }
-                 } finally {
-                     unparker.accept(thread);
-                     thread.join();
-                     assertNull(exception.get());
+                 synchronized (LockHolder.lock) {
+                     vthread.start();
+                     // wait for thread to start and block
+                     awaitTrue(started);
+                     await(vthread, Thread.State.BLOCKED);
                  }
              } finally {
+                 vthread.join();
                  recording.stop();
              }
  
-             Map<String, Integer> events = sumEvents(recording);
-             System.err.println(events);
+             assertContainsPinnedEvent(recording, vthread);
+         }
+     }
+ 
+     /**
+      * Test jdk.VirtualThreadPinned event when waiting for a class initializer.
+      */
+     @Test
+     void testWaitingForClassInitializer() throws Exception {
+         class TestClass {
+             static {
+                 LockSupport.park();
+             }
+             static void m() {
+                 // do nothing
+             }
+         }
+ 
+         try (Recording recording = new Recording()) {
+             recording.enable("jdk.VirtualThreadPinned");
+             recording.start();
+ 
+             var started1 = new AtomicBoolean();
+             var started2 = new AtomicBoolean();
+ 
+             Thread vthread1 =  Thread.ofVirtual().unstarted(() -> {
+                 started1.set(true);
+                 TestClass.m();
+             });
+             Thread vthread2 = Thread.ofVirtual().unstarted(() -> {
+                 started2.set(true);
+                 TestClass.m();
+             });
  
-             // should have at least one pinned event
-             int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
-             assertTrue(pinnedCount >= 1, "Expected one or more events");
+             try {
+                 // start first virtual thread and wait for it to start + park
+                 vthread1.start();
+                 awaitTrue(started1);
+                 await(vthread1, Thread.State.WAITING);
+ 
+                 // start second virtual thread and wait for it to start
+                 vthread2.start();
+                 awaitTrue(started2);
+ 
+                 // give time for second virtual thread to wait on the MutexLocker
+                 Thread.sleep(3000);
+ 
+             } finally {
+                 LockSupport.unpark(vthread1);
+                 vthread1.join();
+                 vthread2.join();
+                 recording.stop();
+             }
+ 
+             // the recording should have a pinned event for vthread2
+             assertContainsPinnedEvent(recording, vthread2);
          }
      }
  
      /**
       * Test jdk.VirtualThreadSubmitFailed event.

@@ -208,13 +374,11 @@
                  // start a thread
                  Thread thread = factory.newThread(LockSupport::park);
                  thread.start();
  
                  // wait for thread to park
-                 while (thread.getState() != Thread.State.WAITING) {
-                     Thread.sleep(10);
-                 }
+                 await(thread, Thread.State.WAITING);
  
                  // shutdown scheduler
                  pool.shutdown();
  
                  // unpark, the submit should fail

@@ -230,18 +394,27 @@
                  } catch (RejectedExecutionException expected) { }
              } finally {
                  recording.stop();
              }
  
-             Map<String, Integer> events = sumEvents(recording);
-             System.err.println(events);
- 
-             int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0);
-             assertEquals(2, count);
+             List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
+             System.err.println(submitFailedEvents);
+             assertTrue(submitFailedEvents.size() == 2, "Expected two events");
          }
      }
  
+     /**
+      * Returns the list of events in the given recording with the given name.
+      */
+     private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
+         Path recordingFile = recordingFile(recording);
+         return RecordingFile.readAllEvents(recordingFile)
+                 .stream()
+                 .filter(e -> e.getEventType().getName().equals(name))
+                 .toList();
+     }
+ 
      /**
       * Read the events from the recording and return a map of event name to count.
       */
      private static Map<String, Integer> sumEvents(Recording recording) throws IOException {
          Path recordingFile = recordingFile(recording);

@@ -262,6 +435,40 @@
              recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
              recording.dump(recordingFile);
          }
          return recordingFile;
      }
+ 
+     /**
+      * Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
+      */
+     private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
+         List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
+         assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
+         System.err.println(pinnedEvents);
+ 
+         long tid = thread.threadId();
+         assertTrue(pinnedEvents.stream()
+                         .anyMatch(e -> e.getThread().getJavaThreadId() == tid),
+                 "jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
+     }
+ 
+     /**
+      * Waits for the given boolean to be set to true.
+      */
+     private void awaitTrue(AtomicBoolean b) throws InterruptedException {
+         while (!b.get()) {
+             Thread.sleep(10);
+         }
+     }
+ 
+     /**
+      * Waits for the given thread to reach a given state.
+      */
+     private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
+         Thread.State state = thread.getState();
+         while (state != expectedState) {
+             Thread.sleep(10);
+             state = thread.getState();
+         }
+     }
  }
< prev index next >