diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml
index e8e127486ca..b63dfdfedb1 100644
--- a/src/hotspot/share/prims/jvmti.xml
+++ b/src/hotspot/share/prims/jvmti.xml
@@ -3084,6 +3084,9 @@ err = (*jvmti)->Deallocate(jvmti, stack_info);
Thread was not suspended and was not the current thread.
+
+ There is already a frame pop event request at the specified depth.
+
diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp
index debfc77c32c..ec269e60e1b 100644
--- a/src/hotspot/share/prims/jvmtiEnvBase.cpp
+++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp
@@ -1359,7 +1359,11 @@ JvmtiEnvBase::set_frame_pop(JvmtiThreadState* state, javaVFrame* jvf, jint depth
}
assert(jvf->frame_pointer() != nullptr, "frame pointer mustn't be null");
int frame_number = (int)get_frame_count(jvf);
- state->env_thread_state((JvmtiEnvBase*)this)->set_frame_pop(frame_number);
+ JvmtiEnvThreadState* ets = state->env_thread_state(this);
+ if (ets->is_frame_pop(frame_number)) {
+ return JVMTI_ERROR_DUPLICATE;
+ }
+ ets->set_frame_pop(frame_number);
return JVMTI_ERROR_NONE;
}
diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/MethodExitTest/libMethodExitTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/MethodExitTest/libMethodExitTest.cpp
index b7077b10c81..f5e2b267a9f 100644
--- a/test/hotspot/jtreg/serviceability/jvmti/vthread/MethodExitTest/libMethodExitTest.cpp
+++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/MethodExitTest/libMethodExitTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2025, 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
@@ -452,13 +452,25 @@ VirtualThreadMount(jvmtiEnv *jvmti, ...) {
mname = get_method_name(jvmti, jni, method);
cname = get_method_class_name(jvmti, jni, method);
+ print_frame_event_info(jvmti, jni, thread, method, "VirtualThreadMount", ++vthread_mounted_count);
+
LOG("\nHit #%d: VirtualThreadMount #%d: enabling FramePop for method: %s::%s on virtual thread: %p\n",
- brkptBreakpointHit, ++vthread_mounted_count, cname, mname, (void*)thread);
+ brkptBreakpointHit, vthread_mounted_count, cname, mname, (void*)thread);
err = jvmti->NotifyFramePop(thread, 0);
check_jvmti_status(jni, err, "VirtualThreadMount: error in JVMTI NotifyFramePop");
- print_frame_event_info(jvmti, jni, thread, method, "VirtualThreadMount", vthread_mounted_count);
+ LOG("\nHit #%d: VirtualThreadMount #%d: enabling duplicated FramePop for method: %s::%s on virtual thread: %p\n",
+ brkptBreakpointHit, vthread_mounted_count, cname, mname, (void*)thread);
+
+ err = jvmti->NotifyFramePop(thread, 0);
+ if (err == JVMTI_ERROR_DUPLICATE) {
+ LOG("NotifyFramePop at VirtualThreadUnmount event returned expected JVMTI_ERROR_DUPLICATE\n");
+ } else {
+ LOG("Failed: NotifyFramePop at VirtualThreadUnmount returned %s(%d) instead of expected JVMTI_ERROR_DUPLICATE\n",
+ TranslateError(err), err);
+ jni->FatalError("NotifyFramePop error: expected error code JVMTI_ERROR_DUPLICATE");
+ }
// Test SetThreadLocalStorage for virtual thread.
err = jvmti->SetThreadLocalStorage(thread, tls_data2);
@@ -497,13 +509,7 @@ VirtualThreadUnmount(jvmtiEnv *jvmti, ...) {
mname = get_method_name(jvmti, jni, method);
cname = get_method_class_name(jvmti, jni, method);
- LOG("\nHit #%d: VirtualThreadUnmount #%d: enabling FramePop for method: %s::%s on virtual thread: %p\n",
- brkptBreakpointHit, ++vthread_unmounted_count, cname, mname, (void*)thread);
-
- err = jvmti->NotifyFramePop(thread, 0);
- check_jvmti_status(jni, err, "VirtualThreadUnmount: error in JVMTI NotifyFramePop");
-
- print_frame_event_info(jvmti, jni, thread, method, "VirtualThreadUnmount", vthread_unmounted_count);
+ print_frame_event_info(jvmti, jni, thread, method, "VirtualThreadUnmount", ++vthread_unmounted_count);
deallocate(jvmti, jni, (void*)mname);
deallocate(jvmti, jni, (void*)cname);