< prev index next >

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

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2021, 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) 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,27 ***
  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.VThreadPinner;
- import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.params.ParameterizedTest;
! import org.junit.jupiter.params.provider.Arguments;
- import org.junit.jupiter.params.provider.MethodSource;
  import static org.junit.jupiter.api.Assertions.*;
  
  class JfrEvents {
  
      /**
--- 38,23 ---
  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.locks.LockSupport;
  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.VThreadPinner;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.params.ParameterizedTest;
! import org.junit.jupiter.params.provider.ValueSource;
  import static org.junit.jupiter.api.Assertions.*;
  
  class JfrEvents {
  
      /**

*** 91,104 ***
              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
       */
!     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>) () -> {
                  VThreadPinner.runPinned(() -> {
!                     while (!finish1.get()) {
!                         LockSupport.park();
                      }
                  });
!             },
!             Thread.State.WAITING,
!                 (Consumer<Thread>) t -> {
!                     finish1.set(true);
!                     LockSupport.unpark(t);
                  }
!         );
  
!         // timed park with native frame on stack
!         var finish2 = new AtomicBoolean();
!         var timedParkWhenPinned = Arguments.of(
!             "LockSupport.parkNanos when pinned",
!             (ThrowingRunnable<Exception>) () -> {
                  VThreadPinner.runPinned(() -> {
!                     while (!finish2.get()) {
!                         LockSupport.parkNanos(Long.MAX_VALUE);
                      }
                  });
!             },
!             Thread.State.TIMED_WAITING,
!             (Consumer<Thread>) t -> {
!                 finish2.set(true);
!                 LockSupport.unpark(t);
              }
-         );
  
!         return Stream.of(parkWhenPinned, timedParkWhenPinned);
      }
  
      /**
!      * Test jdk.VirtualThreadPinned event.
       */
!     @ParameterizedTest
!     @MethodSource("pinnedCases")
!     void testVirtualThreadPinned(String label,
!                                  ThrowingRunnable<Exception> parker,
!                                  Thread.State expectedState,
!                                  Consumer<Thread> unparker) throws Exception {
  
          try (Recording recording = new Recording()) {
              recording.enable("jdk.VirtualThreadPinned");
  
              recording.start();
              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());
                  }
              } finally {
                  recording.stop();
              }
  
!             Map<String, Integer> events = sumEvents(recording);
!             System.err.println(events);
  
!             // should have at least one pinned event
!             int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
!             assertTrue(pinnedCount >= 1, "Expected one or more events");
          }
      }
  
      /**
       * Test jdk.VirtualThreadSubmitFailed event.
--- 87,263 ---
              assertEquals(100, endCount);
          }
      }
  
      /**
!      * Test jdk.VirtualThreadPinned event when parking while pinned.
       */
!     @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(() -> {
!                     started.set(true);
!                     while (!done.get()) {
+                         if (timed) {
+                             LockSupport.parkNanos(Long.MAX_VALUE);
+                         } else {
+                             LockSupport.park();
+                         }
                      }
                  });
!             });
! 
!             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();
+             }
+ 
+             assertContainsPinnedEvent(recording, vthread);
+         }
+     }
  
!     /**
!      * Test jdk.VirtualThreadPinned event when waiting with Object.wait while pinned.
!      */
!     @ParameterizedTest
!     @ValueSource(booleans = { true, false })
+     void testObjectWait(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(() -> {
!                     started.set(true);
!                     synchronized (lock) {
+                         try {
+                             if (timed) {
+                                 lock.wait(Long.MAX_VALUE);
+                             } else {
+                                 lock.wait();
+                             }
+                         } catch (InterruptedException e) {
+                             fail();
+                         }
                      }
                  });
!             });
! 
!             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();
              }
  
!             assertContainsPinnedEvent(recording, vthread);
+         }
      }
  
      /**
!      * Test jdk.VirtualThreadPinned event when parking in a class initializer.
       */
!     @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 {
!                 synchronized (LockHolder.lock) {
!                     vthread.start();
!                     // wait for thread to start and block
!                     awaitTrue(started);
!                     await(vthread, Thread.State.BLOCKED);
                  }
              } finally {
+                 vthread.join();
                  recording.stop();
              }
  
!             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();
+             });
+ 
+             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 ***
                  // 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);
-                 }
  
                  // shutdown scheduler
                  pool.shutdown();
  
                  // unpark, the submit should fail
--- 363,11 ---
                  // start a thread
                  Thread thread = factory.newThread(LockSupport::park);
                  thread.start();
  
                  // wait for thread to park
!                 await(thread, Thread.State.WAITING);
  
                  // shutdown scheduler
                  pool.shutdown();
  
                  // unpark, the submit should fail

*** 230,18 ***
                  } 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);
          }
      }
  
      /**
       * 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);
--- 383,27 ---
                  } catch (RejectedExecutionException expected) { }
              } finally {
                  recording.stop();
              }
  
!             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 ***
--- 424,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 >