Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8295976

GetThreadListStackTraces returns wrong state for blocked VirtualThread

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P3
    • 21
    • 20
    • hotspot
    • b26
    • generic
    • generic

    Description

      ADDITIONAL SYSTEM INFORMATION :
      Ubunu 22.10 x86_64
      openjdk version "20-ea" 2023-03-21
      OpenJDK Runtime Environment (build 20-ea+20-1466)
      OpenJDK 64-Bit Server VM (build 20-ea+20-1466, mixed mode, sharing)


      A DESCRIPTION OF THE PROBLEM :
      GetThreadListStackTraces returns JVMTI_THREAD_STATE_RUNNABLE for a VirtualThread blocked on an intrinsic monitor when called for more than one thread. When called for a single VirtualThread it correctly returns a state that includes the JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER flag.

      It looks like the carrier thread state is not checked in this code path. Maybe VM_GetThreadListStackTraces::doit should call get_threadOop_and_JavaThread instead of cv_external_thread_to_JavaThread.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      With the attached sources and JAVA_HOME set to a JDK 20:

      gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VirtualThreadStateTest.c -o libVirtualThreadStateTest.so

      $JAVA_HOME/bin/javac --enable-preview --release=20 VirtualThreadStateTest.java

      LD_LIBRARY_PATH=. $JAVA_HOME/bin/java --enable-preview -agentlib:VirtualThreadStateTest VirtualThreadStateTest


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Test process should return 0.
      ACTUAL -
      Test process prints "Multiple state wrong" and returns 1.

      ---------- BEGIN SOURCE ----------
      -- VirtualThreadStateTest.java -------------------------------------------------------------
      import java.util.concurrent.locks.ReentrantLock;

      public class VirtualThreadStateTest {
          private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
          private static final int JVMTI_THREAD_STATE_WAITING = 0x0080;

          private static native int getStateSingle(Thread thread);
          private static native int getStateMultiple(Thread thread, Thread other);

          public static void main(String[] args) throws InterruptedException {
              System.loadLibrary("VirtualThreadStateTest");

              checkReentrantLock();
              checkSynchronized();
          }

          private static void checkReentrantLock() throws InterruptedException {
              final ReentrantLock reentrantLock = new ReentrantLock();
              reentrantLock.lock();
              Thread virtualThread = Thread.ofVirtual().start(() -> {
                  System.out.println("trying to get lock");
                  reentrantLock.lock();
                  System.out.println("got lock");
              });
              Thread.sleep(1000);
              checkStates(virtualThread, Thread.State.WAITING);
          }

          private static void checkSynchronized() throws InterruptedException {
              final Object monitor = new Object();
              synchronized (monitor) {
                  Thread virtualThread = Thread.ofVirtual().start(() -> {
                      System.out.println("trying to get monitor");
                      synchronized (monitor) {
                          System.out.println("got monitor");
                      }
                  });
                  Thread.sleep(1000);
                  checkStates(virtualThread, Thread.State.BLOCKED);
              }
          }

          private static void checkStates(Thread virtualThread, Thread.State expected) {
              int stateSingle = getStateSingle(virtualThread);
              int stateMultiple = getStateMultiple(virtualThread, Thread.currentThread());
              System.out.println("State: " + virtualThread.getState() + ", Single: " +
      Integer.toHexString(stateSingle) + ", Multiple: " + Integer.toHexString(stateMultiple));
              if (virtualThread.getState() != expected) {
                  System.out.println("Java state wrong");
                  System.exit(1);
              }
              int jvmtiExpected = expected == Thread.State.WAITING ? JVMTI_THREAD_STATE_WAITING : JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
              if ((stateSingle & jvmtiExpected) == 0) {
                  System.out.println("Single state wrong");
                  System.exit(1);
              }
              if ((stateMultiple & jvmtiExpected) == 0) {
                  System.out.println("Multiple state wrong");
                  System.exit(1);
              }
          }
      }
      --------------------------------------------------------------------------------------------

      -- VirtualThreadStateTest.c -------------------------------------------------------------
      #include <jvmti.h>
      #include <stdlib.h>

      static jvmtiEnv *jvmti = NULL;

      static void checkJvmti(int code, const char* message) {
          if (code != JVMTI_ERROR_NONE) {
              printf("Error %s: %d\n", message, code);
              abort();
          }
      }

      JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateSingle(JNIEnv* jni_env, jclass clazz, jthread virtualThread) {
          jvmtiStackInfo* stackInfo = NULL;
          checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 1, &virtualThread, 1000, &stackInfo), "getStateSingle");
          return stackInfo[0].state;
      }

      JNIEXPORT jint JNICALL Java_VirtualThreadStateTest_getStateMultiple
      (JNIEnv* jni_env, jclass clazz, jthread virtualThread, jthread otherThread)
      {
          jthread threads[2];
          threads[0] = virtualThread;
          threads[1] = otherThread;
          jvmtiStackInfo* stackInfo = NULL;
          checkJvmti((*jvmti)->GetThreadListStackTraces(jvmti, 2, threads, 1000, &stackInfo), "getStateMultiple");
          return stackInfo[0].state;
      }

      JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
          (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION);
          if (jvmti == NULL) {
              printf("could not initialize JVMTI\n");
              abort();
          }
          return JVMTI_ERROR_NONE;
      }
      --------------------------------------------------------------------------------------------
      ---------- END SOURCE ----------

      FREQUENCY : always


      Attachments

        Issue Links

          Activity

            People

              sspitsyn Serguei Spitsyn
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: