diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index ae466071de2..926f7cecb63 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -2958,6 +2958,10 @@ err = (*jvmti)->Deallocate(jvmti, stack_info); The thread whose current frame is to be popped. + The 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. @@ -2967,8 +2971,8 @@ err = (*jvmti)->Deallocate(jvmti, stack_info); The implementation is unable to pop this 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. Thread was not suspended and was not the current thread. diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index f0cd545fb11..283a06e2aa3 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -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); diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index 3e549f6e836..563eb39f60c 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -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 diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index 2a3d19feb98..66ef137957b 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -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 diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/BoundVThreadTest/libBoundVThreadTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/BoundVThreadTest/libBoundVThreadTest.cpp index 4d94d6c9e27..6245f2acefe 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/BoundVThreadTest/libBoundVThreadTest.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/BoundVThreadTest/libBoundVThreadTest.cpp @@ -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); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/PopFrameTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/PopFrameTest.java new file mode 100644 index 00000000000..f3500fec4a9 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/PopFrameTest.java @@ -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); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/libPopFrameTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/libPopFrameTest.cpp new file mode 100644 index 00000000000..20e4b940649 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/PopFrameTest/libPopFrameTest.cpp @@ -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 +#include +#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" diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadUnsupportedTest/libVThreadUnsupportedTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadUnsupportedTest/libVThreadUnsupportedTest.cpp index dca558b1e68..2ec30dc4a5a 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadUnsupportedTest/libVThreadUnsupportedTest.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadUnsupportedTest/libVThreadUnsupportedTest.cpp @@ -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);