8295976: GetThreadListStackTraces returns wrong state for blocked VirtualThread

Reviewed-by: cjplummer, amenkov
This commit is contained in:
Serguei Spitsyn 2023-06-07 07:51:37 +00:00
parent fadcd65018
commit a25b7b8b55
5 changed files with 220 additions and 7 deletions
src/hotspot/share/prims
test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadListStackTracesTest

@ -1780,9 +1780,9 @@ JvmtiEnv::GetAllStackTraces(jint max_frame_count, jvmtiStackInfo** stack_info_pt
jvmtiError
JvmtiEnv::GetThreadListStackTraces(jint thread_count, const jthread* thread_list, jint max_frame_count, jvmtiStackInfo** stack_info_ptr) {
jvmtiError err = JVMTI_ERROR_NONE;
JvmtiVTMSTransitionDisabler disabler;
if (thread_count == 1) {
JvmtiVTMSTransitionDisabler disabler;
// Use direct handshake if we need to get only one stack trace.
JavaThread *current_thread = JavaThread::current();

@ -1344,13 +1344,15 @@ JvmtiEnvBase::current_thread_obj_or_resolve_external_guard(jthread thread) {
}
jvmtiError
JvmtiEnvBase::get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread,
JvmtiEnvBase::get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread, JavaThread* cur_thread,
JavaThread** jt_pp, oop* thread_oop_p) {
JavaThread* cur_thread = JavaThread::current();
JavaThread* java_thread = nullptr;
oop thread_oop = nullptr;
if (thread == nullptr) {
if (cur_thread == nullptr) { // cur_thread can be null when called from a VM_op
return JVMTI_ERROR_INVALID_THREAD;
}
java_thread = cur_thread;
thread_oop = get_vthread_or_thread_oop(java_thread);
if (thread_oop == nullptr || !thread_oop->is_a(vmClasses::Thread_klass())) {
@ -1381,6 +1383,14 @@ JvmtiEnvBase::get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread,
return JVMTI_ERROR_NONE;
}
jvmtiError
JvmtiEnvBase::get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread,
JavaThread** jt_pp, oop* thread_oop_p) {
JavaThread* cur_thread = JavaThread::current();
jvmtiError err = get_threadOop_and_JavaThread(t_list, thread, cur_thread, jt_pp, thread_oop_p);
return err;
}
// Check for JVMTI_ERROR_NOT_SUSPENDED and JVMTI_ERROR_OPAQUE_FRAME errors.
// Used in PopFrame and ForceEarlyReturn implementations.
jvmtiError
@ -1931,13 +1941,15 @@ VM_GetThreadListStackTraces::doit() {
jthread jt = _thread_list[i];
JavaThread* java_thread = nullptr;
oop thread_oop = nullptr;
jvmtiError err = JvmtiExport::cv_external_thread_to_JavaThread(tlh.list(), jt, &java_thread, &thread_oop);
jvmtiError err = JvmtiEnvBase::get_threadOop_and_JavaThread(tlh.list(), jt, nullptr, &java_thread, &thread_oop);
if (err != JVMTI_ERROR_NONE) {
// We got an error code so we don't have a JavaThread *, but
// only return an error from here if we didn't get a valid
// thread_oop.
// In the virtual thread case the cv_external_thread_to_JavaThread is expected to correctly set
// the thread_oop and return JVMTI_ERROR_INVALID_THREAD which we ignore here.
// In the virtual thread case the get_threadOop_and_JavaThread is expected to correctly set
// the thread_oop and return JVMTI_ERROR_THREAD_NOT_ALIVE which we ignore here.
// The corresponding thread state will be recorded in the jvmtiStackInfo.state.
if (thread_oop == nullptr) {
_collector.set_result(err);
return;
@ -1952,7 +1964,7 @@ VM_GetThreadListStackTraces::doit() {
void
GetSingleStackTraceClosure::do_thread(Thread *target) {
JavaThread *jt = JavaThread::cast(target);
oop thread_oop = jt->threadObj();
oop thread_oop = JNIHandles::resolve_external_guard(_jthread);
if (!jt->is_exiting() && thread_oop != nullptr) {
ResourceMark rm;

@ -214,6 +214,8 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
return result;
}
static jvmtiError get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread, JavaThread* cur_thread,
JavaThread** jt_pp, oop* thread_oop_p);
static jvmtiError get_threadOop_and_JavaThread(ThreadsList* t_list, jthread thread,
JavaThread** jt_pp, oop* thread_oop_p);

@ -0,0 +1,135 @@
/*
* Copyright (c) 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.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8295976
* @summary GetThreadListStackTraces returns wrong state for blocked VirtualThread
* @requires vm.continuations
* @run main/othervm/native -agentlib:ThreadListStackTracesTest ThreadListStackTracesTest
*/
import java.util.concurrent.locks.ReentrantLock;
abstract class TestTask implements Runnable {
volatile boolean threadReady = false;
static void log(String msg) { System.out.println(msg); }
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
}
}
public void ensureReady(Thread vt, Thread.State expState) {
// wait while the thread is not ready or thread state is unexpected
while (!threadReady || (vt.getState() != expState)) {
sleep(1);
}
}
public abstract void run();
}
class ReentrantLockTestTask extends TestTask {
public void run() {
log("grabbing reentrantLock");
threadReady = true;
ThreadListStackTracesTest.reentrantLock.lock();
log("grabbed reentrantLock");
}
}
class ObjectMonitorTestTask extends TestTask {
public void run() {
log("entering synchronized statement");
threadReady = true;
synchronized (ThreadListStackTracesTest.objectMonitor) {
log("entered synchronized statement");
}
}
}
public class ThreadListStackTracesTest {
static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400;
static final int JVMTI_THREAD_STATE_WAITING = 0x0080;
static final ReentrantLock reentrantLock = new ReentrantLock();
static final Object objectMonitor = new Object();
private static native int getStateSingle(Thread thread);
private static native int getStateMultiple(Thread thread, Thread other);
static void log(String msg) { System.out.println(msg); }
static void failed(String msg) { throw new RuntimeException(msg); }
public static void main(String[] args) throws InterruptedException {
checkReentrantLock();
checkSynchronized();
}
private static void checkReentrantLock() throws InterruptedException {
final Thread.State expState = Thread.State.WAITING;
reentrantLock.lock();
String name = "ReentrantLockTestTask";
TestTask task = new ReentrantLockTestTask();
Thread vt = Thread.ofVirtual().name(name).start(task);
task.ensureReady(vt, expState);
checkStates(vt, expState);
}
private static void checkSynchronized() throws InterruptedException {
final Thread.State expState = Thread.State.BLOCKED;
synchronized (objectMonitor) {
String name = "ObjectMonitorTestTask";
TestTask task = new ObjectMonitorTestTask();
Thread vt = Thread.ofVirtual().name(name).start(task);
task.ensureReady(vt, expState);
checkStates(vt, expState);
}
}
private static void checkStates(Thread vt, Thread.State expState) {
int singleState = getStateSingle(vt);
int multiState = getStateMultiple(vt, Thread.currentThread());
int jvmtiExpState = (expState == Thread.State.WAITING) ?
JVMTI_THREAD_STATE_WAITING :
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
System.out.printf("State: expected: %s single: %x multi: %x\n",
vt.getState(), singleState, multiState);
if (vt.getState() != expState) {
failed("Java thread state is wrong");
}
if ((singleState & jvmtiExpState) == 0) {
failed("JVMTI single thread state is wrong");
}
if ((multiState & jvmtiExpState) == 0) {
failed("JVMTI multi thread state is wrong");
}
}
}

@ -0,0 +1,64 @@
/*
* Copyright (c) 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.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <jni.h>
#include <jvmti.h>
#include <stdio.h>
#include <string.h>
#include "jvmti_common.h"
static jvmtiEnv* jvmti = nullptr;
static const jint MAX_FRAME_COUNT = 32;
extern "C" {
JNIEXPORT jint JNICALL
Java_ThreadListStackTracesTest_getStateSingle(JNIEnv* jni, jclass clazz, jthread vthread) {
jvmtiStackInfo* info = NULL;
jvmtiError err = jvmti->GetThreadListStackTraces(1, &vthread, MAX_FRAME_COUNT, &info);
check_jvmti_status(jni, err, "getStateSingle: error in JVMTI GetThreadListStackTraces");
return info[0].state;
}
JNIEXPORT jint JNICALL
Java_ThreadListStackTracesTest_getStateMultiple(JNIEnv* jni, jclass clazz, jthread vhread, jthread other) {
jthread threads[2] = { vhread, other };
jvmtiStackInfo* info = NULL;
jvmtiError err = jvmti->GetThreadListStackTraces(2, threads, MAX_FRAME_COUNT, &info);
check_jvmti_status(jni, err, "getStateMultiple: error in JVMTI GetThreadListStackTraces");
return info[0].state;
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) {
LOG("Agent_OnLoad: error in GetEnv");
return JNI_ERR;
}
return 0;
}
} // extern "C"