Skip to content

Commit

Permalink
8295976: GetThreadListStackTraces returns wrong state for blocked Vir…
Browse files Browse the repository at this point in the history
…tualThread

Reviewed-by: cjplummer, amenkov
  • Loading branch information
Serguei Spitsyn committed Jun 7, 2023
1 parent fadcd65 commit a25b7b8
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/hotspot/share/prims/jvmtiEnv.cpp
Expand Up @@ -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();
Expand Down
24 changes: 18 additions & 6 deletions src/hotspot/share/prims/jvmtiEnvBase.cpp
Expand Up @@ -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())) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/prims/jvmtiEnvBase.hpp
Expand Up @@ -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);

Expand Down
@@ -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"

1 comment on commit a25b7b8

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.