8306034: add support of virtual threads to JVMTI StopThread

Reviewed-by: cjplummer
This commit is contained in:
Serguei Spitsyn 2023-05-11 17:48:39 +00:00
parent 489658dbd2
commit 51b8f3cfb9
11 changed files with 517 additions and 24 deletions

@ -1892,7 +1892,7 @@ jvmtiEnv *jvmti;
<function id="StopThread" num="7">
<synopsis>Stop Thread</synopsis>
<description>
Send the specified asynchronous exception to the specified platform thread.
Send the specified asynchronous exception to the specified thread.
</description>
<origin>jvmdi</origin>
<capabilities>
@ -1903,8 +1903,10 @@ jvmtiEnv *jvmti;
<jthread impl="noconvert"/>
<description>
The thread to stop.
The <code>thread</code> may not be a virtual thread. Otherwise, the error code
<errorlink id="JVMTI_ERROR_UNSUPPORTED_OPERATION"></errorlink> will be returned.
The <functionlink id="StopThread"></functionlink> function may be used to send
an asynchronous exception to a virtual thread when it is suspended at an event.
An implementation may support sending an asynchronous exception to a suspended
virtual thread in other cases.
</description>
</param>
<param id="exception">
@ -1915,8 +1917,12 @@ jvmtiEnv *jvmti;
</param>
</parameters>
<errors>
<error id="JVMTI_ERROR_UNSUPPORTED_OPERATION">
<paramlink id="thread"/> is a virtual thread.
<error id="JVMTI_ERROR_THREAD_NOT_SUSPENDED">
Thread is a virtual thread and was not suspended and was not the current thread.
</error>
<error id="JVMTI_ERROR_OPAQUE_FRAME">
The thread is a suspended virtual thread and the implementation was unable
to throw an asynchronous exception from the current frame.
</error>
</errors>
</function>
@ -11975,7 +11981,8 @@ myInit() {
There are no Java programming language or JNI stack frames at the specified depth.
</errorid>
<errorid id="JVMTI_ERROR_OPAQUE_FRAME" num="32">
Information about the frame is not available (e.g. for native frames).
Information about the frame is not available (e.g. for native frames),
or the function cannot be performed on the thread's current frame.
</errorid>
<errorid id="JVMTI_ERROR_DUPLICATE" num="40">
Item already set.

@ -1190,9 +1190,15 @@ JvmtiEnv::StopThread(jthread thread, jobject exception) {
jvmtiError err = get_threadOop_and_JavaThread(tlh.list(), thread, &java_thread, &thread_oop);
if (thread_oop != nullptr && thread_oop->is_a(vmClasses::BaseVirtualThread_klass())) {
// No support for virtual threads (yet).
return JVMTI_ERROR_UNSUPPORTED_OPERATION;
bool is_virtual = thread_oop != nullptr && thread_oop->is_a(vmClasses::BaseVirtualThread_klass());
if (is_virtual && !is_JavaThread_current(java_thread, thread_oop)) {
if (!is_vthread_suspended(thread_oop, java_thread)) {
return JVMTI_ERROR_THREAD_NOT_SUSPENDED;
}
if (java_thread == nullptr) { // unmounted virtual thread
return JVMTI_ERROR_OPAQUE_FRAME;
}
}
if (err != JVMTI_ERROR_NONE) {
return err;

@ -1319,6 +1319,19 @@ JvmtiEnvBase::is_cthread_with_continuation(JavaThread* jt) {
return cont_entry != nullptr && is_cthread_with_mounted_vthread(jt);
}
// Check if VirtualThread or BoundVirtualThread is suspended.
bool
JvmtiEnvBase::is_vthread_suspended(oop vt_oop, JavaThread* jt) {
bool suspended = false;
if (java_lang_VirtualThread::is_instance(vt_oop)) {
suspended = JvmtiVTSuspender::is_vthread_suspended(vt_oop);
}
if (vt_oop->is_a(vmClasses::BoundVirtualThread_klass())) {
suspended = jt->is_suspended();
}
return suspended;
}
// If (thread == null) then return current thread object.
// Otherwise return JNIHandles::resolve_external_guard(thread).
oop

@ -221,6 +221,9 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
static bool is_cthread_with_mounted_vthread(JavaThread* jt);
static bool is_cthread_with_continuation(JavaThread* jt);
// Check if VirtualThread or BoundVirtualThread is suspended.
static bool is_vthread_suspended(oop vt_oop, JavaThread* jt);
static JvmtiEnv* JvmtiEnv_from_jvmti_env(jvmtiEnv *env) {
return (JvmtiEnv*)((intptr_t)env - in_bytes(jvmti_external_offset()));
};

@ -1100,9 +1100,12 @@ void JavaThread::install_async_exception(AsyncExceptionHandshake* aeh) {
// for AbortVMOnException flag
Exceptions::debug_check_abort(exception->klass()->external_name());
// Interrupt thread so it will wake up from a potential wait()/sleep()/park()
java_lang_Thread::set_interrupted(threadObj(), true);
this->interrupt();
oop vt_oop = vthread();
if (vt_oop == nullptr || !vt_oop->is_a(vmClasses::BaseVirtualThread_klass())) {
// Interrupt thread so it will wake up from a potential wait()/sleep()/park()
java_lang_Thread::set_interrupted(threadObj(), true);
this->interrupt();
}
}
class InstallAsyncExceptionHandshake : public HandshakeClosure {

@ -79,6 +79,7 @@ vmTestbase/nsk/jdb/where/where005/where005.java 8278470 generic-all
vmTestbase/nsk/jdb/list/list003/list003.java 8300707 generic-all
vmTestbase/nsk/jdb/repeat/repeat001/repeat001.java 8300707 generic-all
vmTestbase/nsk/jdb/kill/kill001/kill001.java 8306467 generic-all
####
## NSK JDI tests failing with wrapper

@ -115,10 +115,6 @@ test_unsupported_jvmti_functions(jvmtiEnv *jvmti, JNIEnv *jni, jthread vthread,
fatal(jni, "Virtual threads are not supported");
}
LOG("Testing StopThread\n");
err = jvmti->StopThread(vthread, vthread);
check_jvmti_error_unsupported_operation(jni, "StopThread", err);
LOG("Testing PopFrame\n");
err = jvmti->PopFrame(vthread);
check_jvmti_error_opaque_frame(jni, "PopFrame", err);

@ -0,0 +1,272 @@
/*
* 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 id=default
* @summary Verifies JVMTI StopThread support for virtual threads.
* @requires vm.continuations
* @run main/othervm/native -agentlib:StopThreadTest StopThreadTest
*/
/*
* @test id=no-vmcontinuations
* @summary Verifies JVMTI StopThread support for bound virtual threads.
* @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StopThreadTest
*/
/*
* @test id=platform
* @summary Verifies JVMTI StopThread support for platform threads.
* @run main/othervm/native -agentlib:StopThreadTest StopThreadTest platform
*/
import java.lang.AssertionError;
/*
* The test exercises the JVMTI function: StopThread(jthread).
* The test creates a new virtual or platform thread.
* Its method run() invokes the following methods:
* - method A() that is blocked on a monitor
* - method B() that is stopped at a breakpoint
* - method C() that forces agent to send AssertionError exception to its own thread
* All cases are using JVMTI StopThread to send an AssertionError object.
*/
public class StopThreadTest {
private static final String agentLib = "StopThreadTest";
static final int JVMTI_ERROR_NONE = 0;
static final int THREAD_NOT_SUSPENDED = 13;
static final int PASSED = 0;
static final int FAILED = 2;
static void log(String str) { System.out.println(str); }
static native void prepareAgent(Class taskClass, Object exceptionObject);
static native void suspendThread(Thread thread);
static native void resumeThread(Thread thread);
static native void ensureAtBreakpoint();
static native void notifyAtBreakpoint();
static native int stopThread(Thread thread);
static int status = PASSED;
static boolean is_virtual = true;
static void setFailed(String msg) {
log("\nFAILED: " + msg);
status = FAILED;
}
static void throwFailed(String msg) {
log("\nFAILED: " + msg);
throw new RuntimeException("StopThreadTest failed!");
}
public static void main(String args[]) {
is_virtual = !(args.length > 0 && args[0].equals("platform"));
run();
if (status == FAILED) {
throwFailed("StopThreadTest!");
}
log("\nStopThreadTest passed");
}
public static void run() {
TestTask testTask = new TestTask();
Thread testTaskThread = null;
AssertionError excObject = new AssertionError();
int retCode;
prepareAgent(TestTask.class, excObject);
log("\nMain #A: method A() must be blocked on entering a synchronized statement");
synchronized (TestTask.lock) {
if (is_virtual) {
testTaskThread = Thread.ofVirtual().name("TestTaskThread").start(testTask);
} else {
testTaskThread = Thread.ofPlatform().name("TestTaskThread").start(testTask);
}
testTask.ensureStarted();
if (is_virtual) { // this check is for virtual target thread only
log("\nMain #A.1: unsuspended");
retCode = stopThread(testTaskThread);
if (retCode != THREAD_NOT_SUSPENDED) {
throwFailed("Main #A.1: expected THREAD_NOT_SUSPENDED instead of: " + retCode);
} else {
log("Main #A.1: got expected THREAD_NOT_SUSPENDED");
}
}
log("\nMain #A.2: suspended");
suspendThread(testTaskThread);
retCode = stopThread(testTaskThread);
if (retCode != JVMTI_ERROR_NONE) {
throwFailed("Main #A.2: expected JVMTI_ERROR_NONE instead of: " + retCode);
} else {
log("Main #A.2: got expected JVMTI_ERROR_NONE");
}
resumeThread(testTaskThread);
}
log("\nMain #B: method B() must be blocked in a breakpoint event handler");
{
ensureAtBreakpoint();
if (is_virtual) { // this check is for virtual target thread only
log("\nMain #B.1: unsuspended");
retCode = stopThread(testTaskThread);
if (retCode != THREAD_NOT_SUSPENDED) {
throwFailed("Main #B.1: expected THREAD_NOT_SUSPENDED instead of: " + retCode);
}
}
log("\nMain #B.2: suspended");
suspendThread(testTaskThread);
retCode = stopThread(testTaskThread);
if (retCode != JVMTI_ERROR_NONE) {
throwFailed("Main #B.2: expected JVMTI_ERROR_NONE");
}
resumeThread(testTaskThread);
notifyAtBreakpoint();
}
log("\nMain #C: method C() sends AssertionError object to its own thread");
{
// StopThread is called from the test task (own thread) and expected to succeed.
// No suspension of the test task thread is required or can be done in this case.
testTask.ensureFinished();
}
try {
testTaskThread.join();
} catch (InterruptedException ex) {
throwFailed("Unexpected " + ex);
}
}
static class TestTask implements Runnable {
static Object lock = new Object();
static void log(String str) { System.out.println(str); }
private volatile boolean started = false;
private volatile boolean finished = false;
static public void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
}
}
// Ensure thread is ready.
public void ensureStarted() {
while (!started) {
sleep(1);
}
}
// Ensure thread is finished.
public void ensureFinished() {
while (!finished) {
sleep(1);
}
}
public void run() {
log("TestTask.run: started");
started = true;
boolean seenExceptionFromA = false;
try {
A();
} catch (AssertionError ex) {
log("TestTask.run: caught expected AssertionError from method A()");
seenExceptionFromA = true;
if (!Thread.currentThread().isVirtual()) { // platform thread
// clear the interrupt status
Thread.interrupted();
}
}
if (!seenExceptionFromA) {
StopThreadTest.setFailed("TestTask.run: expected AssertionError from method A()");
}
sleep(1); // to cause yield
boolean seenExceptionFromB = false;
try {
B();
} catch (AssertionError ex) {
log("TestTask.run: caught expected AssertionError from method B()");
seenExceptionFromB = true;
if (!Thread.currentThread().isVirtual()) { // platform thread
// clear the interrupt status
Thread.interrupted();
}
}
if (!seenExceptionFromB) {
StopThreadTest.setFailed("TestTask.run: expected AssertionError from method B()");
}
sleep(1); // to cause yield
boolean seenExceptionFromC = false;
try {
C();
} catch (AssertionError ex) {
log("TestTask.run: caught expected AssertionError from method C()");
seenExceptionFromC = true;
}
if (!seenExceptionFromC) {
StopThreadTest.setFailed("TestTask.run: expected AssertionError from method C()");
}
finished = true;
}
// Method is blocked on entering a synchronized statement.
// StopThread is used to send an AssertionError object two times:
// - when not suspended: THREAD_NOT_SUSPENDED is expected
// - when suspended: JVMTI_ERROR_NONE is expected
static void A() {
log("TestTask.A: started");
synchronized (lock) {
}
log("TestTask.A: finished");
}
// A breakpoint is set at start of this method.
// StopThread is used to send an AssertionError object two times:
// - when not suspended: THREAD_NOT_SUSPENDED is expected
// - when suspended: expected to succeed
static void B() {
log("TestTask.B: started");
}
// This method uses StopThread to send an AssertionError object to
// its own thread. It is expected to succeed.
static void C() {
log("TestTask.C: started");
StopThreadTest.stopThread(Thread.currentThread());
log("TestTask.C: finished");
}
}
}

@ -0,0 +1,195 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "jvmti.h"
#include "jvmti_common.h"
extern "C" {
static jvmtiEnv *jvmti = NULL;
static jmethodID mid_B = NULL;
static jobject exception_obj = NULL;
static jrawMonitorID monitor = NULL;
static volatile bool bp_sync_reached = false;
static void JNICALL
Breakpoint(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread,
jmethodID method, jlocation location) {
jvmtiError err;
if (method != mid_B) {
fatal(jni, "Breakpoint: Failed with wrong location: expected in method TestTask.B()");
}
err = jvmti->ClearBreakpoint(mid_B, 0);
check_jvmti_status(jni, err, "Breakpoint: Failed in JVMTI ClearBreakpoint");
LOG("Breakpoint: In method TestTask.B(): before sync section enter\n");
err = jvmti->RawMonitorEnter(monitor);
check_jvmti_status(jni, err, "Breakpoint: Failed in RawMonitorEnter");
bp_sync_reached = true;
// wait for notify from notifyAtBreakpoint or JVMTI_ERROR_INTERRUPT from JVMTI StopThread
err = jvmti->RawMonitorWait(monitor, 0);
if (err == JVMTI_ERROR_INTERRUPT) {
LOG("Breakpoint: In method TestTask.B(): expected JVMTI_ERROR_INTERRUPT from RawMonitorWait\n");
} else {
check_jvmti_status(jni, err, "Breakpoint: Failed in RawMonitorWait");
}
err = jvmti->RawMonitorExit(monitor);
check_jvmti_status(jni, err, "Breakpoint: Failed in RawMonitorExit");
LOG("Breakpoint: In method TestTask.B(): after sync section exit\n");
}
jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
static jvmtiCapabilities caps;
static jvmtiEventCallbacks callbacks;
jvmtiError err;
jint res;
LOG("Agent init\n");
res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);
if (res != JNI_OK || jvmti == NULL) {
LOG("Agent init: Failed in GetEnv!\n");
return JNI_ERR;
}
err = jvmti->GetPotentialCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
LOG("Agent init: Failed in GetPotentialCapabilities: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
err = jvmti->AddCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
LOG("Agent init: Failed in AddCapabilities: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
err = jvmti->GetCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
LOG("Agent init: Failed in GetCapabilities: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
if (!caps.can_generate_breakpoint_events) {
LOG("Agent init: Failed: Breakpoint event is not implemented\n");
return JNI_ERR;
}
callbacks.Breakpoint = &Breakpoint;
err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
if (err != JVMTI_ERROR_NONE) {
LOG("Agent init: Failed in SetEventCallbacks: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
monitor = create_raw_monitor(jvmti, "Raw monitor to test");
return JNI_OK;
}
extern JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
return Agent_Initialize(jvm, options, reserved);
}
JNIEXPORT void JNICALL
Java_StopThreadTest_prepareAgent(JNIEnv *jni, jclass cls, jclass task_clazz, jobject exc_obj) {
jvmtiError err;
LOG("Main: prepareAgent started\n");
if (jvmti == NULL) {
fatal(jni, "prepareAgent: Failed as JVMTI client was not properly loaded!\n");
}
exception_obj = jni->NewGlobalRef(exc_obj);
if (exception_obj == NULL) {
fatal(jni, "prepareAgent: Failed in JNI NewGlobalRef\n");
}
mid_B = jni->GetStaticMethodID(task_clazz, "B", "()V");
if (mid_B == NULL) {
fatal(jni, "prepareAgent: Failed to find Method ID for method: TestTask.B()\n");
}
err = jvmti->SetBreakpoint(mid_B, 0);
check_jvmti_status(jni, err, "prepareAgent: Failed in JVMTI SetBreakpoint");
set_event_notification_mode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, NULL);
LOG("Main: prepareAgent finished\n");
}
JNIEXPORT void JNICALL
Java_StopThreadTest_suspendThread(JNIEnv *jni, jclass cls, jthread thread) {
LOG("Main: suspendThread\n");
suspend_thread(jvmti, jni, thread);
}
JNIEXPORT void JNICALL
Java_StopThreadTest_resumeThread(JNIEnv *jni, jclass cls, jthread thread) {
LOG("Main: resumeThread\n");
resume_thread(jvmti, jni, thread);
}
JNIEXPORT jint JNICALL
Java_StopThreadTest_stopThread(JNIEnv *jni, jclass cls, jthread thread) {
jvmtiError err = jvmti->StopThread(thread, exception_obj);
LOG("Main: stopThread: StopThread returned code: %s (%d)\n", TranslateError(err), err);
return (jint)err;
}
JNIEXPORT void JNICALL
Java_StopThreadTest_ensureAtBreakpoint(JNIEnv *jni, jclass cls) {
jvmtiError err;
bool need_stop = false;
LOG("Main: ensureAtBreakpoint\n");
while (!need_stop) {
err = jvmti->RawMonitorEnter(monitor);
check_jvmti_status(jni, err, "ensureAtBreakpoint: Failed in RawMonitorEnter");
need_stop = bp_sync_reached;
err = jvmti->RawMonitorExit(monitor);
check_jvmti_status(jni, err, "ensureAtBreakpoint: Failed in RawMonitorExit");
sleep_ms(1); // 1 millisecond
}
}
JNIEXPORT void JNICALL
Java_StopThreadTest_notifyAtBreakpoint(JNIEnv *jni, jclass cls) {
jvmtiError err;
LOG("Main: notifyAtBreakpoint\n");
err = jvmti->RawMonitorEnter(monitor);
check_jvmti_status(jni, err, "notifyAtBreakpoint: Fatal Error in RawMonitorEnter");
err = jvmti->RawMonitorNotify(monitor);
check_jvmti_status(jni, err, "notifyAtBreakpoint: Fatal Error in RawMonitorNotify");
err = jvmti->RawMonitorExit(monitor);
check_jvmti_status(jni, err, "notifyAtBreakpoint: Fatal Error in RawMonitorExit");
}
} // extern "C"

@ -23,7 +23,8 @@
/**
* @test
* @summary Verifies that specific JVMTI functions returns JVMTI_ERROR_INVALID_THREAD if called with virtual threads.
* @summary Verifies that specific JVMTI functions return UNSUPPORTED_OPERATION
* or OPAQUE_FRAME if called with virtual threads.
* @requires vm.continuations
* @compile VThreadUnsupportedTest.java
* @run main/othervm/native -agentlib:VThreadUnsupportedTest VThreadUnsupportedTest

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 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
@ -60,8 +60,8 @@ Java_VThreadUnsupportedTest_isCompletedTestInEvent(JNIEnv *env, jobject obj) {
}
/*
* Execute JVMTI functions which currently don't support vthreads and check that they
* return error code JVMTI_ERROR_INVALID_THREAD or JVMTI_ERROR_OPAQUE_FRAME correctly.
* Execute JVMTI functions that don't support vthreads and check they return error
* code JVMTI_ERROR_UNSUPPORTED_OPERATION or JVMTI_ERROR_OPAQUE_FRAME correctly.
*/
static void
test_unsupported_jvmti_functions(jvmtiEnv *jvmti, JNIEnv *jni, jthread vthread) {
@ -87,10 +87,6 @@ test_unsupported_jvmti_functions(jvmtiEnv *jvmti, JNIEnv *jni, jthread vthread)
LOG("Testing JVMTI functions which should not accept a virtual thread argument\n");
LOG("Testing StopThread\n");
err = jvmti->StopThread(vthread, vthread);
check_jvmti_error_unsupported_operation(jni, "StopThread", err);
LOG("Testing PopFrame\n");
err = jvmti->PopFrame(vthread);
check_jvmti_error_opaque_frame(jni, "PopFrame", err);