8308000: add PopFrame support for virtual threads

Reviewed-by: lmesnik, alanb
This commit is contained in:
Serguei Spitsyn 2023-05-22 07:34:35 +00:00
parent 41beb448d2
commit 928fcf9751
8 changed files with 451 additions and 20 deletions

View File

@ -2958,6 +2958,10 @@ err = (*jvmti)->Deallocate(jvmti, stack_info);
<jthread impl="noconvert"/>
<description>
The thread whose current frame is to be popped.
The <functionlink id="PopFrame"></functionlink> function may be used to
pop the current frame of a virtual thread when it is suspended at an event.
An implementation may support popping the current frame of a suspended
virtual thread in other cases.
</description>
</param>
</parameters>
@ -2967,8 +2971,8 @@ err = (*jvmti)-&gt;Deallocate(jvmti, stack_info);
The implementation is unable to pop this frame.
</error>
<error id="JVMTI_ERROR_OPAQUE_FRAME">
The thread is a virtual thread and the implementation is unable
to pop this frame.
The thread is a suspended virtual thread and the implementation
was unable to pop the current frame.
</error>
<error id="JVMTI_ERROR_THREAD_NOT_SUSPENDED">
Thread was not suspended and was not the current thread.

View File

@ -1883,13 +1883,26 @@ JvmtiEnv::PopFrame(jthread thread) {
oop thread_obj = nullptr;
jvmtiError err = get_threadOop_and_JavaThread(tlh.list(), thread, &java_thread, &thread_obj);
if (thread_obj != nullptr && thread_obj->is_a(vmClasses::BaseVirtualThread_klass())) {
// No support for virtual threads (yet).
return JVMTI_ERROR_OPAQUE_FRAME;
}
if (err != JVMTI_ERROR_NONE) {
return err;
}
bool is_virtual = thread_obj != nullptr && thread_obj->is_a(vmClasses::BaseVirtualThread_klass());
if (is_virtual) {
if (!is_JavaThread_current(java_thread, thread_obj)) {
if (!is_vthread_suspended(thread_obj, java_thread)) {
return JVMTI_ERROR_THREAD_NOT_SUSPENDED;
}
if (java_thread == nullptr) { // unmounted virtual thread
return JVMTI_ERROR_OPAQUE_FRAME;
}
}
} else { // platform thread
if (java_thread != current_thread && !java_thread->is_suspended() &&
!java_thread->is_carrier_thread_suspended()) {
return JVMTI_ERROR_THREAD_NOT_SUSPENDED;
}
}
// retrieve or create the state
JvmtiThreadState* state = JvmtiThreadState::state_for(java_thread);

View File

@ -2211,11 +2211,6 @@ UpdateForPopTopFrameClosure::doit(Thread *target, bool self) {
}
assert(java_thread == _state->get_thread(), "Must be");
if (!self && !java_thread->is_suspended() && !java_thread->is_carrier_thread_suspended()) {
_result = JVMTI_ERROR_THREAD_NOT_SUSPENDED;
return;
}
// Check to see if a PopFrame was already in progress
if (java_thread->popframe_condition() != JavaThread::popframe_inactive) {
// Probably possible for JVMTI clients to trigger this, but the

View File

@ -45,7 +45,6 @@ vmTestbase/nsk/jvmti/GetCurrentThreadCpuTime/curthrcputime001/TestDescription.ja
vmTestbase/nsk/jvmti/GetThreadCpuTime/thrcputime001/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/NotifyFramePop/nframepop002/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/NotifyFramePop/nframepop003/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/PopFrame/popframe004/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/StopThread/stopthrd006/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t012/TestDescription.java 8300708 generic-all
vmTestbase/nsk/jvmti/SetLocalVariable/setlocal004/TestDescription.java 8300708 generic-all
@ -119,6 +118,8 @@ vmTestbase/nsk/jdi/ThreadReference/popFrames/popframes002/TestDescription.java 8
vmTestbase/nsk/jdi/ThreadReference/popFrames/popframes003/TestDescription.java 8285414 generic-all
vmTestbase/nsk/jdi/ThreadReference/popFrames/popframes004/TestDescription.java 8285414 generic-all
vmTestbase/nsk/jdi/ThreadReference/popFrames/popframes001/TestDescription.java 8308237 generic-all
####
## JVMTI ForceEarlyReturn not supported for vthreads (JVMTI_ERROR_OPAQUE_FRAME)
## Note forceEarlyReturn002 was converted to support vthreads. The rest were not

View File

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

View File

@ -0,0 +1,253 @@
/*
* 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 PopFrame support for virtual threads.
* @requires vm.continuations
* @run main/othervm/native -agentlib:PopFrameTest PopFrameTest
*/
/*
* @test id=no-vmcontinuations
* @summary Verifies JVMTI PopFrame support for bound virtual threads.
* @run main/othervm/native -agentlib:PopFrameTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations PopFrameTest
*/
/*
* @test id=platform
* @summary Verifies JVMTI PopFrame support for platform threads.
* @run main/othervm/native -agentlib:PopFrameTest PopFrameTest platform
*/
import java.lang.AssertionError;
/*
* The test exercises the JVMTI function PopFrame.
* 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 call PopFrame on its own thread
* JVMTI PopFrame is called in all cases.
*/
public class PopFrameTest {
private static final String agentLib = "PopFrameTest";
static final int JVMTI_ERROR_NONE = 0;
static final int THREAD_NOT_SUSPENDED = 13;
static final int OPAQUE_FRAME = 32;
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, boolean doPopFrame);
static native void suspendThread(Thread thread);
static native void resumeThread(Thread thread);
static native void ensureAtBreakpoint();
static native void notifyAtBreakpoint();
static native int popFrame(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("PopFrameTest failed!");
}
public static void main(String args[]) {
is_virtual = !(args.length > 0 && args[0].equals("platform"));
run();
if (status == FAILED) {
throwFailed("PopFrameTest!");
}
log("\nPopFrameTest passed");
}
public static void run() {
TestTask testTask = new TestTask();
Thread testTaskThread = null;
int errCode;
log("\nMain #A: method A() must be blocked on entering a synchronized statement");
if (is_virtual) {
testTaskThread = Thread.ofVirtual().name("TestTaskThread").start(testTask);
} else {
testTaskThread = Thread.ofPlatform().name("TestTaskThread").start(testTask);
}
{
TestTask.ensureAtPointA();
log("\nMain #A.1: unsuspended");
errCode = popFrame(testTaskThread);
if (errCode != THREAD_NOT_SUSPENDED) {
throwFailed("Main #A.1: expected THREAD_NOT_SUSPENDED instead of: " + errCode);
} else {
log("Main #A.1: got expected THREAD_NOT_SUSPENDED");
}
log("\nMain #A.2: suspended");
suspendThread(testTaskThread);
errCode = popFrame(testTaskThread);
if (errCode != JVMTI_ERROR_NONE) {
throwFailed("Main #A.2: expected JVMTI_ERROR_NONE instead of: " + errCode);
} else {
log("Main #A.2: got expected JVMTI_ERROR_NONE");
}
resumeThread(testTaskThread);
TestTask.clearDoLoop();
TestTask.sleep(5);
}
log("\nMain #B: method B() must be blocked in a breakpoint event handler");
{
ensureAtBreakpoint();
log("\nMain #B.1: unsuspended");
errCode = popFrame(testTaskThread);
if (errCode != THREAD_NOT_SUSPENDED) {
throwFailed("Main #B.1: expected THREAD_NOT_SUSPENDED instead of: " + errCode);
}
log("Main #B.1: got expected THREAD_NOT_SUSPENDED");
log("\nMain #B.2: suspended");
suspendThread(testTaskThread);
errCode = popFrame(testTaskThread);
if (errCode != JVMTI_ERROR_NONE) {
throwFailed("Main #B.2: expected JVMTI_ERROR_NONE");
}
log("Main #B.2: got expected JVMTI_ERROR_NONE");
resumeThread(testTaskThread);
notifyAtBreakpoint();
log("\nMain #B.3: unsuspended, call PopFrame on own thread");
ensureAtBreakpoint();
notifyAtBreakpoint();
TestTask.sleep(5);
}
log("\nMain #C: method C() calls PopFrame on its own thread");
{
// PopFrame is called from method C() on own thread. Expected to return OPAQUE_FRAME.
// 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 void log(String str) { System.out.println(str); }
static volatile boolean doLoop = true;
static volatile boolean atPointA = false;
static volatile boolean finished = false;
static 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.
static void ensureAtPointA() {
while (!atPointA) {
sleep(1);
}
}
// Ensure thread is finished.
static void ensureFinished() {
while (!finished) {
sleep(1);
}
}
static void clearDoLoop() {
doLoop = false;
}
public void run() {
log("TestTask.run: started");
A();
sleep(1); // to cause yield
prepareAgent(TestTask.class, false); // No doPopFrame
B();
sleep(1); // to cause yield
prepareAgent(TestTask.class, true); // doPopFrame
B();
sleep(1); // to cause yield
C();
finished = true;
}
// Method is busy in a while loop.
// PopFrame is used two times:
// - when not suspended: THREAD_NOT_SUSPENDED is expected
// - when suspended: JVMTI_ERROR_NONE is expected
static void A() {
log("TestTask.A: started");
atPointA = true;
while (doLoop) {
}
log("TestTask.A: finished");
}
// A breakpoint is set at start of this method.
// PopFrame is used 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 PopFrame on its own thread. It is expected to return OPAQUE_FRAME.
static void C() {
log("TestTask.C: started");
int errCode = PopFrameTest.popFrame(Thread.currentThread());
if (errCode == OPAQUE_FRAME) {
log("TestTask.C: got expected OPAQUE_FRAME");
} else {
setFailed("TestTask.C: expected OPAQUE_FRAME from PopFrame instead of: " + errCode);
}
}
}
}

View File

@ -0,0 +1,173 @@
/*
* 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;
static jmethodID mid_B;
static jrawMonitorID monitor;
static jboolean do_pop_frame;
static volatile bool bp_sync_reached;
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\n");
{
RawMonitorLocker rml(jvmti, jni, monitor);
bp_sync_reached = true;
rml.wait(0);
}
LOG("Breakpoint: In method TestTask.B(): after sync section\n");
if (do_pop_frame != 0) {
err = jvmti->PopFrame(thread);
LOG("Breakpoint: PopFrame returned code: %s (%d)\n", TranslateError(err), err);
check_jvmti_status(jni, err, "Breakpoint: Failed in PopFrame");
}
LOG("Breakpoint: In method TestTask.B() finished\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_PopFrameTest_prepareAgent(JNIEnv *jni, jclass cls, jclass task_clazz, jboolean do_pop) {
jvmtiError err;
LOG("Main: prepareAgent started\n");
if (jvmti == NULL) {
fatal(jni, "prepareAgent: Failed as JVMTI client was not properly loaded!\n");
}
do_pop_frame = do_pop;
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_PopFrameTest_suspendThread(JNIEnv *jni, jclass cls, jthread thread) {
LOG("Main: suspendThread\n");
suspend_thread(jvmti, jni, thread);
}
JNIEXPORT void JNICALL
Java_PopFrameTest_resumeThread(JNIEnv *jni, jclass cls, jthread thread) {
LOG("Main: resumeThread\n");
resume_thread(jvmti, jni, thread);
}
JNIEXPORT jint JNICALL
Java_PopFrameTest_popFrame(JNIEnv *jni, jclass cls, jthread thread) {
jvmtiError err = jvmti->PopFrame(thread);
LOG("Main: popFrame: PopFrame returned code: %s (%d)\n", TranslateError(err), err);
return (jint)err;
}
JNIEXPORT void JNICALL
Java_PopFrameTest_ensureAtBreakpoint(JNIEnv *jni, jclass cls) {
bool need_stop = false;
LOG("Main: ensureAtBreakpoint\n");
while (!need_stop) {
RawMonitorLocker rml(jvmti, jni, monitor);
need_stop = bp_sync_reached;
sleep_ms(1); // 1 millisecond
}
}
JNIEXPORT void JNICALL
Java_PopFrameTest_notifyAtBreakpoint(JNIEnv *jni, jclass cls) {
LOG("Main: notifyAtBreakpoint\n");
RawMonitorLocker rml(jvmti, jni, monitor);
bp_sync_reached = false;
rml.notify_all();
}
} // extern "C"

View File

@ -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 PopFrame\n");
err = jvmti->PopFrame(vthread);
check_jvmti_error_opaque_frame(jni, "PopFrame", err);
LOG("Testing ForceEarlyReturnVoid\n");
err = jvmti->ForceEarlyReturnVoid(vthread);
check_jvmti_error_opaque_frame(jni, "ForceEarlyReturnVoid", err);