Событие jdk.VirtualThreadPinned не перехвачено

Учитывая этот код, он пытается прослушать событие jdk.VirtualThreadPinned, когда виртуальный поток закреплен:

import static org.junit.Assert.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.consumer.RecordingStream;
import org.junit.Test;

public class JFRTest {

    @Test
    public void pinnedVirtualThreads() throws InterruptedException {
        CountDownLatch stopListening = new CountDownLatch(1);
        CountDownLatch startListening = new CountDownLatch(1);
        try (RecordingStream recordingStream = new RecordingStream()) {
            // Create and start the recording stream
            startRecordingStream(recordingStream, startListening, stopListening);
    
            // Make sure JFR listener is running before continue
            while (true) {
                new StartEvent().commit();
                if (startListening.await(1, TimeUnit.SECONDS)) {
                    break;
                }
            }
    
            // Simulate blocking operation
            Thread vt = Thread.ofVirtual().start(() -> {
                synchronized (this) {
                    System.out.println("Virtual Thread is pinned");
                }
            });
            vt.join();
        }
        // Wait till the jdk.VirtualThreadPinned triggers
        assertTrue("jdk.VirtualThreadPinned was not sent", stopListening.await(5, TimeUnit.SECONDS));
    }
    
    private void startRecordingStream(RecordingStream recordingStream, CountDownLatch startListening, CountDownLatch stopListening) {
        // Enable the jdk.VirtualThreadPinned event
        recordingStream.enable("jdk.VirtualThreadPinned").withStackTrace();

        // Notify listener is running after receiving the StartEvent
        recordingStream.onEvent("StartEvent", event -> {
            System.out.println("Received " + event);
            startListening.countDown();
        });

        // Set an event handler
        recordingStream.onEvent("jdk.VirtualThreadPinned", event -> {
            System.out.println("VirtualThreadPinned event detected!");
            System.out.println("Timestamp: " + event);
            stopListening.countDown();
        });

        // Start the recording stream
        recordingStream.startAsync();
    }

    @Name("StartEvent")
    private static class StartEvent extends Event {
    }
}

Это результат:

[INFO] Running com.example.general.JFRTest
Received StartEvent {
  startTime = 10:37:46.830 (2024-06-12)
  eventThread = "main" (javaThreadId = 1)
  stackTrace = [
    com.example.general.JFRTest.pinnedVirtualThreads() line: 25
    jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Object, Object[]) line: 103
    java.lang.reflect.Method.invoke(Object, Object[]) line: 580
    org.junit.runners.model.FrameworkMethod$1.runReflectiveCall() line: 59
    org.junit.internal.runners.model.ReflectiveCallable.run() line: 12
  ]
}


Virtual Thread is pinned
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 6.215 s <<< FAILURE! - in com.example.general.JFRTest
[ERROR] com.example.general.JFRTest.pinnedVirtualThreads  Time elapsed: 6.187 s  <<< FAILURE!
java.lang.AssertionError: jdk.VirtualThreadPinned was not sent

Как видите, эта часть должна вызвать событие jdk.VirtualThreadPinned, но этого не происходит. С другой стороны, ловится другое событие StartEvent:

Thread vt = Thread.ofVirtual().start(() -> {
    synchronized (this) {
        System.out.println("Virtual Thread is pinned");
    }
});

Я не знаю, почему.

Это моя версия JDK: 21.0.3+7-LTS-152.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
0
91
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

jdk.VirtualThreadPinned запускается только тогда, когда среда выполнения пытается припарковать закрепленный поток. Вы можете вызвать это, например, вызвав Thread.sleep с запущенным виртуальным потоком:

Thread vt = Thread.ofVirtual().start(() -> {
    synchronized (lock) {
        System.out.println("Virtual Thread is pinned");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
});

Вам также необходимо убедиться, что поток событий сброшен, иначе ваш обратный вызов для события jdk.VirtualThreadPinned никогда не будет вызван. Вы можете сделать это, вызвав .stop() в потоке записи (close()-включение потока резко останавливает запись и не ждет, пока будут обработаны все события). Например, прямо перед концом блока try:

    vt.join();
    recordingStream.stop();
} // end of try

С этими двумя изменениями я вижу, что событие запускается:

VirtualThreadPinned event detected!
Timestamp: jdk.VirtualThreadPinned {
  startTime = 10:28:32.075 (2024-06-12)
  duration = 998 ms
  eventThread = "" (javaThreadId = 37, virtual)
  stackTrace = [
    java.lang.VirtualThread.parkOnCarrierThread(boolean, long) line: 689
    java.lang.VirtualThread.parkNanos(long) line: 648
    java.lang.VirtualThread.sleepNanos(long) line: 812
    java.lang.Thread.sleepNanos(long) line: 489
    java.lang.Thread.sleep(long) line: 522
    ...
  ]
}

Я вижу, вы как-то связаны с этой темой. У меня есть еще один вопрос: почему трассировка стека обрезается до 5 строк? См. jdk.jfr.consumer.RecordedObject#toString. Наличие полного стека на самом деле является целью понять, почему поток закреплен.

ravenskater 13.06.2024 10:14

Другой вопрос, который у меня возникает: как определить в трассировке стека, где находится монитор?

ravenskater 13.06.2024 10:53

Другие вопросы по теме