From fda142ff6cfefa12ec1ea4d4eb48b3c1b285bc04 Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Tue, 12 Sep 2023 02:46:47 +0000 Subject: [PATCH] 8312174: missing JVMTI events from vthreads parked during JVMTI attach Reviewed-by: lmesnik, amenkov --- .../share/prims/jvmtiEventController.cpp | 31 ++- .../share/prims/jvmtiEventController.hpp | 1 + src/hotspot/share/prims/jvmtiExport.cpp | 90 ++++---- src/hotspot/share/prims/jvmtiExport.hpp | 5 + src/hotspot/share/prims/jvmtiThreadState.hpp | 3 + .../share/prims/jvmtiThreadState.inline.hpp | 1 + .../VThreadEventTest/VThreadEventTest.java | 210 ++++++++++++++++++ .../VThreadEventTest/libVThreadEventTest.cpp | 104 +++++++++ test/lib/jdk/test/lib/jvmti/jvmti_common.h | 7 + 9 files changed, 404 insertions(+), 48 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp diff --git a/src/hotspot/share/prims/jvmtiEventController.cpp b/src/hotspot/share/prims/jvmtiEventController.cpp index a063389b6df..1d895573a5d 100644 --- a/src/hotspot/share/prims/jvmtiEventController.cpp +++ b/src/hotspot/share/prims/jvmtiEventController.cpp @@ -309,6 +309,8 @@ public: static void clear_to_frame_pop(JvmtiEnvThreadState *env_thread, JvmtiFramePop fpop); static void change_field_watch(jvmtiEvent event_type, bool added); + static bool is_any_thread_filtered_event_enabled_globally(); + static void recompute_thread_filtered(JvmtiThreadState *state); static void thread_started(JavaThread *thread); static void thread_ended(JavaThread *thread); @@ -730,6 +732,20 @@ JvmtiEventControllerPrivate::recompute_enabled() { EC_TRACE(("[-] # recompute enabled - after " JULONG_FORMAT_X, any_env_thread_enabled)); } +bool +JvmtiEventControllerPrivate::is_any_thread_filtered_event_enabled_globally() { + julong global_thread_events = JvmtiEventController::_universal_global_event_enabled.get_bits() & THREAD_FILTERED_EVENT_BITS; + return global_thread_events != 0L; +} + +void +JvmtiEventControllerPrivate::recompute_thread_filtered(JvmtiThreadState *state) { + assert(Threads::number_of_threads() == 0 || JvmtiThreadState_lock->is_locked(), "sanity check"); + + if (is_any_thread_filtered_event_enabled_globally()) { + JvmtiEventControllerPrivate::recompute_thread_enabled(state); + } +} void JvmtiEventControllerPrivate::thread_started(JavaThread *thread) { @@ -739,17 +755,11 @@ JvmtiEventControllerPrivate::thread_started(JavaThread *thread) { EC_TRACE(("[%s] # thread started", JvmtiTrace::safe_get_thread_name(thread))); // if we have any thread filtered events globally enabled, create/update the thread state - if ((JvmtiEventController::_universal_global_event_enabled.get_bits() & THREAD_FILTERED_EVENT_BITS) != 0) { - MutexLocker mu(JvmtiThreadState_lock); - // create the thread state if missing - JvmtiThreadState *state = JvmtiThreadState::state_for_while_locked(thread); - if (state != nullptr) { // skip threads with no JVMTI thread state - recompute_thread_enabled(state); - } + if (is_any_thread_filtered_event_enabled_globally()) { // intentionally racy + JvmtiThreadState::state_for(thread); } } - void JvmtiEventControllerPrivate::thread_ended(JavaThread *thread) { // Removes the JvmtiThreadState associated with the specified thread. @@ -1115,6 +1125,11 @@ JvmtiEventController::change_field_watch(jvmtiEvent event_type, bool added) { JvmtiEventControllerPrivate::change_field_watch(event_type, added); } +void +JvmtiEventController::recompute_thread_filtered(JvmtiThreadState *state) { + JvmtiEventControllerPrivate::recompute_thread_filtered(state); +} + void JvmtiEventController::thread_started(JavaThread *thread) { // operates only on the current thread diff --git a/src/hotspot/share/prims/jvmtiEventController.hpp b/src/hotspot/share/prims/jvmtiEventController.hpp index 9b236b29204..84070a3098c 100644 --- a/src/hotspot/share/prims/jvmtiEventController.hpp +++ b/src/hotspot/share/prims/jvmtiEventController.hpp @@ -234,6 +234,7 @@ public: static void change_field_watch(jvmtiEvent event_type, bool added); + static void recompute_thread_filtered(JvmtiThreadState *state); static void thread_started(JavaThread *thread); static void thread_ended(JavaThread *thread); diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index b0c48b84b44..a24a339e5b7 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -417,6 +417,15 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { } } +JvmtiThreadState* +JvmtiExport::get_jvmti_thread_state(JavaThread *thread) { + assert(thread == JavaThread::current(), "must be current thread"); + if (thread->is_vthread_mounted() && thread->jvmti_thread_state() == nullptr) { + JvmtiEventController::thread_started(thread); + } + return thread->jvmti_thread_state(); +} + void JvmtiExport::add_default_read_edges(Handle h_module, TRAPS) { if (!Universe::is_module_initialized()) { @@ -920,7 +929,7 @@ class JvmtiClassFileLoadHookPoster : public StackObj { _has_been_modified = false; assert(!_thread->is_in_any_VTMS_transition(), "CFLH events are not allowed in any VTMS transition"); - _state = _thread->jvmti_thread_state(); + _state = JvmtiExport::get_jvmti_thread_state(_thread); if (_state != nullptr) { _class_being_redefined = _state->get_class_being_redefined(); _load_kind = _state->get_class_load_kind(); @@ -1212,7 +1221,7 @@ void JvmtiExport::post_raw_breakpoint(JavaThread *thread, Method* method, addres HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1310,7 +1319,7 @@ void JvmtiExport::at_single_stepping_point(JavaThread *thread, Method* method, a methodHandle mh(thread, method); // update information about current location and post a step event - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1329,7 +1338,7 @@ void JvmtiExport::at_single_stepping_point(JavaThread *thread, Method* method, a void JvmtiExport::expose_single_stepping(JavaThread *thread) { - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state != nullptr) { state->clear_hide_single_stepping(); } @@ -1337,7 +1346,7 @@ void JvmtiExport::expose_single_stepping(JavaThread *thread) { bool JvmtiExport::hide_single_stepping(JavaThread *thread) { - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state != nullptr && state->is_enabled(JVMTI_EVENT_SINGLE_STEP)) { state->set_hide_single_stepping(); return true; @@ -1352,7 +1361,7 @@ void JvmtiExport::post_class_load(JavaThread *thread, Klass* klass) { } HandleMark hm(thread); - JvmtiThreadState* state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1390,7 +1399,7 @@ void JvmtiExport::post_class_prepare(JavaThread *thread, Klass* klass) { } HandleMark hm(thread); - JvmtiThreadState* state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1519,7 +1528,7 @@ void JvmtiExport::post_thread_end(JavaThread *thread) { EVT_TRIG_TRACE(JVMTI_EVENT_THREAD_END, ("[%s] Trg Thread End event triggered", JvmtiTrace::safe_get_thread_name(thread))); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1567,7 +1576,7 @@ void JvmtiExport::post_vthread_start(jobject vthread) { EVT_TRIG_TRACE(JVMTI_EVENT_VIRTUAL_THREAD_START, ("[%p] Trg Virtual Thread Start event triggered", vthread)); JavaThread *cur_thread = JavaThread::current(); - JvmtiThreadState *state = cur_thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(cur_thread); if (state == nullptr) { return; } @@ -1601,7 +1610,7 @@ void JvmtiExport::post_vthread_end(jobject vthread) { EVT_TRIG_TRACE(JVMTI_EVENT_VIRTUAL_THREAD_END, ("[%p] Trg Virtual Thread End event triggered", vthread)); JavaThread *cur_thread = JavaThread::current(); - JvmtiThreadState *state = cur_thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(cur_thread); if (state == nullptr) { return; } @@ -1636,7 +1645,7 @@ void JvmtiExport::post_vthread_mount(jobject vthread) { HandleMark hm(thread); EVT_TRIG_TRACE(EXT_EVENT_VIRTUAL_THREAD_MOUNT, ("[%p] Trg Virtual Thread Mount event triggered", vthread)); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1671,7 +1680,7 @@ void JvmtiExport::post_vthread_unmount(jobject vthread) { HandleMark hm(thread); EVT_TRIG_TRACE(EXT_EVENT_VIRTUAL_THREAD_UNMOUNT, ("[%p] Trg Virtual Thread Unmount event triggered", vthread)); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1704,7 +1713,7 @@ void JvmtiExport::continuation_yield_cleanup(JavaThread* thread, jint continuati } assert(thread == JavaThread::current(), "must be"); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -1798,7 +1807,7 @@ void JvmtiExport::post_method_entry(JavaThread *thread, Method* method, frame cu HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState* state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr || !state->is_interp_only_mode()) { // for any thread that actually wants method entry, interp_only_mode is set return; @@ -1838,7 +1847,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr || !state->is_interp_only_mode()) { // for any thread that actually wants method exit, interp_only_mode is set @@ -1959,7 +1968,7 @@ void JvmtiExport::post_single_step(JavaThread *thread, Method* method, address l HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2001,7 +2010,7 @@ void JvmtiExport::post_exception_throw(JavaThread *thread, Method* method, addre // ensure the stack is sufficiently processed. KeepStackGCProcessedMark ksgcpm(thread); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2089,7 +2098,7 @@ void JvmtiExport::notice_unwind_due_to_exception(JavaThread *thread, Method* met methodHandle mh(thread, method); Handle exception_handle(thread, exception); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2205,7 +2214,7 @@ void JvmtiExport::post_field_access(JavaThread *thread, Method* method, HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2361,7 +2370,7 @@ void JvmtiExport::post_field_modification(JavaThread *thread, Method* method, HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2603,7 +2612,7 @@ void JvmtiExport::post_dynamic_code_generated_while_holding_locks(const char* na // jvmti thread state. // The collector and/or state might be null if JvmtiDynamicCodeEventCollector // has been initialized while JVMTI_EVENT_DYNAMIC_CODE_GENERATED was disabled. - JvmtiThreadState* state = thread->jvmti_thread_state(); + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state != nullptr) { JvmtiDynamicCodeEventCollector *collector = state->get_dynamic_code_event_collector(); if (collector != nullptr) { @@ -2722,7 +2731,10 @@ void JvmtiExport::post_data_dump() { void JvmtiExport::post_monitor_contended_enter(JavaThread *thread, ObjectMonitor *obj_mntr) { oop object = obj_mntr->object(); - JvmtiThreadState *state = thread->jvmti_thread_state(); + HandleMark hm(thread); + Handle h(thread, object); + + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2730,9 +2742,6 @@ void JvmtiExport::post_monitor_contended_enter(JavaThread *thread, ObjectMonitor return; // no events should be posted if thread is in any VTMS transition } - HandleMark hm(thread); - Handle h(thread, object); - EVT_TRIG_TRACE(JVMTI_EVENT_MONITOR_CONTENDED_ENTER, ("[%s] monitor contended enter event triggered", JvmtiTrace::safe_get_thread_name(thread))); @@ -2755,7 +2764,10 @@ void JvmtiExport::post_monitor_contended_enter(JavaThread *thread, ObjectMonitor void JvmtiExport::post_monitor_contended_entered(JavaThread *thread, ObjectMonitor *obj_mntr) { oop object = obj_mntr->object(); - JvmtiThreadState *state = thread->jvmti_thread_state(); + HandleMark hm(thread); + Handle h(thread, object); + + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2763,9 +2775,6 @@ void JvmtiExport::post_monitor_contended_entered(JavaThread *thread, ObjectMonit return; // no events should be posted if thread is in any VTMS transition } - HandleMark hm(thread); - Handle h(thread, object); - EVT_TRIG_TRACE(JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, ("[%s] monitor contended entered event triggered", JvmtiTrace::safe_get_thread_name(thread))); @@ -2789,7 +2798,10 @@ void JvmtiExport::post_monitor_contended_entered(JavaThread *thread, ObjectMonit void JvmtiExport::post_monitor_wait(JavaThread *thread, oop object, jlong timeout) { - JvmtiThreadState *state = thread->jvmti_thread_state(); + HandleMark hm(thread); + Handle h(thread, object); + + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2797,9 +2809,6 @@ void JvmtiExport::post_monitor_wait(JavaThread *thread, oop object, return; // no events should be posted if thread is in any VTMS transition } - HandleMark hm(thread); - Handle h(thread, object); - EVT_TRIG_TRACE(JVMTI_EVENT_MONITOR_WAIT, ("[%s] monitor wait event triggered", JvmtiTrace::safe_get_thread_name(thread))); @@ -2823,7 +2832,10 @@ void JvmtiExport::post_monitor_wait(JavaThread *thread, oop object, void JvmtiExport::post_monitor_waited(JavaThread *thread, ObjectMonitor *obj_mntr, jboolean timed_out) { oop object = obj_mntr->object(); - JvmtiThreadState *state = thread->jvmti_thread_state(); + HandleMark hm(thread); + Handle h(thread, object); + + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2831,9 +2843,6 @@ void JvmtiExport::post_monitor_waited(JavaThread *thread, ObjectMonitor *obj_mnt return; // no events should be posted if thread is in any VTMS transition } - HandleMark hm(thread); - Handle h(thread, object); - EVT_TRIG_TRACE(JVMTI_EVENT_MONITOR_WAITED, ("[%s] monitor waited event triggered", JvmtiTrace::safe_get_thread_name(thread))); @@ -2886,7 +2895,10 @@ void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) { } void JvmtiExport::post_sampled_object_alloc(JavaThread *thread, oop object) { - JvmtiThreadState *state = thread->jvmti_thread_state(); + HandleMark hm(thread); + Handle h(thread, object); + + JvmtiThreadState *state = get_jvmti_thread_state(thread); if (state == nullptr) { return; } @@ -2896,8 +2908,6 @@ void JvmtiExport::post_sampled_object_alloc(JavaThread *thread, oop object) { if (thread->is_in_any_VTMS_transition()) { return; // no events should be posted if thread is in any VTMS transition } - HandleMark hm(thread); - Handle h(thread, object); EVT_TRIG_TRACE(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, ("[%s] Trg sampled object alloc triggered", diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index a35d6dfc067..263104f74f8 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -298,6 +298,11 @@ class JvmtiExport : public AllStatic { static void decode_version_values(jint version, int * major, int * minor, int * micro) NOT_JVMTI_RETURN; + // If the jvmti_thread_state is absent and any thread filtered event + // is enabled globally then it is created. + // Otherwise, the thread->jvmti_thread_state() is returned. + static JvmtiThreadState* get_jvmti_thread_state(JavaThread *thread); + // single stepping management methods static void at_single_stepping_point(JavaThread *thread, Method* method, address location) NOT_JVMTI_RETURN; static void expose_single_stepping(JavaThread *thread) NOT_JVMTI_RETURN; diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index 8340a44d142..4dc24487058 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -465,9 +465,12 @@ class JvmtiThreadState : public CHeapObj { // already holding JvmtiThreadState_lock - retrieve or create JvmtiThreadState // Can return null if JavaThread is exiting. + // Callers are responsible to call recompute_thread_filtered() to update event bits + // if thread-filtered events are enabled globally. static JvmtiThreadState *state_for_while_locked(JavaThread *thread, oop thread_oop = nullptr); // retrieve or create JvmtiThreadState // Can return null if JavaThread is exiting. + // Calls recompute_thread_filtered() to update event bits if thread-filtered events are enabled globally. static JvmtiThreadState *state_for(JavaThread *thread, Handle thread_handle = Handle()); // JVMTI ForceEarlyReturn support diff --git a/src/hotspot/share/prims/jvmtiThreadState.inline.hpp b/src/hotspot/share/prims/jvmtiThreadState.inline.hpp index bbcbe14e56e..1737bfd6a9f 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.inline.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.inline.hpp @@ -109,6 +109,7 @@ inline JvmtiThreadState* JvmtiThreadState::state_for(JavaThread *thread, Handle MutexLocker mu(JvmtiThreadState_lock); // check again with the lock held state = state_for_while_locked(thread, thread_handle()); + JvmtiEventController::recompute_thread_filtered(state); } else { // Check possible safepoint even if state is non-null. // (Note: the thread argument isn't the current thread) diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java new file mode 100644 index 00000000000..554e83c231c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java @@ -0,0 +1,210 @@ +/* + * 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 8312174 + * @summary missing JVMTI events from vthreads parked during JVMTI attach + * @requires vm.continuations + * @requires vm.jvmti + * @requires vm.compMode != "Xcomp" + * @run main/othervm/native + * -Djdk.virtualThreadScheduler.parallelism=9 + * -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach + */ + +import com.sun.tools.attach.VirtualMachine; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.LockSupport; +import java.util.List; +import java.util.ArrayList; + +/* + * The test uses custom implementation of the CountDownLatch class. + * The reason is we want the state of tested thread to be predictable. + * With java.util.concurrent.CountDownLatch it is not clear what thread state is expected. + */ +class CountDownLatch { + private int count = 0; + + CountDownLatch(int count) { + this.count = count; + } + + public synchronized void countDown() { + count--; + notify(); + } + + public synchronized void await() throws InterruptedException { + while (count > 0) { + wait(1); + } + } +} + +public class VThreadEventTest { + static final int TCNT1 = 10; + static final int TCNT2 = 4; + static final int TCNT3 = 4; + static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3; + + private static void log(String msg) { System.out.println(msg); } + + private static native int threadEndCount(); + private static native int threadMountCount(); + private static native int threadUnmountCount(); + + private static volatile boolean attached; + private static boolean failed; + private static List test1Threads = new ArrayList(TCNT1); + + private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT); + private static CountDownLatch ready1 = new CountDownLatch(TCNT1); + private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT); + private static CountDownLatch mready = new CountDownLatch(1); + + private static void await(CountDownLatch dumpedLatch) { + try { + dumpedLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // The test1 vthreads are kept unmounted until interrupted after agent attach. + static final Runnable test1 = () -> { + synchronized (test1Threads) { + test1Threads.add(Thread.currentThread()); + } + log("test1 vthread started"); + ready0.countDown(); + await(mready); + ready1.countDown(); // to guaranty state is not State.WAITING after await(mready) + try { + Thread.sleep(20000); // big timeout to keep unmounted until interrupted + } catch (InterruptedException ex) { + // it is expected, ignore + } + ready2.countDown(); + }; + + // The test2 vthreads are kept mounted until agent attach. + static final Runnable test2 = () -> { + log("test2 vthread started"); + ready0.countDown(); + await(mready); + while (!attached) { + // keep mounted + } + ready2.countDown(); + }; + + // The test3 vthreads are kept mounted until agent attach. + static final Runnable test3 = () -> { + log("test3 vthread started"); + ready0.countDown(); + await(mready); + while (!attached) { + // keep mounted + } + LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount + ready2.countDown(); + }; + + public static void main(String[] args) throws Exception { + if (Runtime.getRuntime().availableProcessors() < 8) { + log("WARNING: test expects at least 8 processors."); + } + try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < TCNT1; i++) { + executorService.execute(test1); + } + for (int i = 0; i < TCNT2; i++) { + executorService.execute(test2); + } + for (int i = 0; i < TCNT3; i++) { + executorService.execute(test3); + } + await(ready0); + mready.countDown(); + await(ready1); // to guaranty state is not State.WAITING after await(mready) in test1() + // wait for test1 threads to reach WAITING state in sleep() + for (Thread t : test1Threads) { + Thread.State state = t.getState(); + log("DBG: state: " + state); + while (state != Thread.State.WAITING) { + Thread.sleep(10); + state = t.getState(); + log("DBG: state: " + state); + } + } + + VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid())); + vm.loadAgentLibrary("VThreadEventTest"); + Thread.sleep(200); // to allow the agent to get ready + + attached = true; + for (Thread t : test1Threads) { + t.interrupt(); + } + ready2.await(); + } + // wait until all VirtualThreadEnd events have been sent + for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) { + Thread.sleep(100); + if (sleepNo % 100 == 0) { // 10 sec period of waiting + log("main: waited seconds: " + sleepNo/10); + } + } + int threadEndCnt = threadEndCount(); + int threadMountCnt = threadMountCount(); + int threadUnmountCnt = threadUnmountCount(); + int threadEndExp = THREAD_CNT; + int threadMountExp = THREAD_CNT - TCNT2; + int threadUnmountExp = THREAD_CNT + TCNT3; + + log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")"); + log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")"); + log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")"); + + if (threadEndCnt != threadEndExp) { + log("FAILED: unexpected count of ThreadEnd events"); + failed = true; + } + if (threadMountCnt != threadMountExp) { + log("FAILED: unexpected count of ThreadMount events"); + failed = true; + } + if (threadUnmountCnt != threadUnmountExp) { + log("FAILED: unexpected count of ThreadUnmount events"); + failed = true; + } + if (failed) { + throw new RuntimeException("FAILED: event count is wrong"); + } + } + +} + diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp new file mode 100644 index 00000000000..24b678eb18e --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/libVThreadEventTest.cpp @@ -0,0 +1,104 @@ +/* + * 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 +#include "jvmti_common.h" + +extern "C" { + +static jvmtiEnv *jvmti = nullptr; +static std::atomic thread_end_cnt(0); +static std::atomic thread_unmount_cnt(0); +static std::atomic thread_mount_cnt(0); + +void JNICALL VirtualThreadEnd(jvmtiEnv *jvmti, JNIEnv* jni, jthread vthread) { + thread_end_cnt++; +} + +void JNICALL VirtualThreadMount(jvmtiEnv* jvmti, ...) { + thread_mount_cnt++; +} + +void JNICALL VirtualThreadUnmount(jvmtiEnv* jvmti, ...) { + thread_unmount_cnt++; +} + +JNIEXPORT jint JNICALL +Java_VThreadEventTest_threadEndCount(JNIEnv* jni, jclass clazz) { + return thread_end_cnt; +} + +JNIEXPORT jint JNICALL +Java_VThreadEventTest_threadMountCount(JNIEnv* jni, jclass clazz) { + return thread_mount_cnt; +} + +JNIEXPORT jint JNICALL +Java_VThreadEventTest_threadUnmountCount(JNIEnv* jni, jclass clazz) { + return thread_unmount_cnt; +} + +JNIEXPORT jint JNICALL +Agent_OnAttach(JavaVM *vm, char *options, void *reserved) { + jvmtiEventCallbacks callbacks; + jvmtiCapabilities caps; + jvmtiError err; + + LOG("Agent_OnAttach started\n"); + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + LOG("Could not initialize JVMTI env\n"); + return JNI_ERR; + } + memset(&caps, 0, sizeof(caps)); + caps.can_support_virtual_threads = 1; + check_jvmti_error(jvmti->AddCapabilities(&caps), "AddCapabilities"); + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.VirtualThreadEnd = &VirtualThreadEnd; + + err = jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks)); + check_jvmti_error(err, "SetEventCallbacks"); + + err = set_ext_event_callback(jvmti, "VirtualThreadMount", VirtualThreadMount); + check_jvmti_error(err, "SetExtEventCallback for VirtualThreadMount"); + + err = set_ext_event_callback(jvmti, "VirtualThreadUnmount", VirtualThreadUnmount); + check_jvmti_error(err, "SetExtEventCallback for VirtualThreadUnmount"); + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_END, nullptr); + check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadEnd"); + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, EXT_EVENT_VIRTUAL_THREAD_MOUNT, nullptr); + check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadMount"); + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, EXT_EVENT_VIRTUAL_THREAD_UNMOUNT, nullptr); + check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount"); + + LOG("vthread events enabled\n"); + return JVMTI_ERROR_NONE; +} + +} // extern "C" + diff --git a/test/lib/jdk/test/lib/jvmti/jvmti_common.h b/test/lib/jdk/test/lib/jvmti/jvmti_common.h index 829e14e2955..6a0bcf25331 100644 --- a/test/lib/jdk/test/lib/jvmti/jvmti_common.h +++ b/test/lib/jdk/test/lib/jvmti/jvmti_common.h @@ -128,6 +128,13 @@ fatal(JNIEnv* jni, const char* msg) { jni->FatalError(msg); } +static void +check_jvmti_error(jvmtiError err, const char* msg) { + if (err != JVMTI_ERROR_NONE) { + LOG("check_jvmti_error: JVMTI function returned error: %s: %s(%d)\n", msg, TranslateError(err), err); + abort(); + } +} static void check_jvmti_status(JNIEnv* jni, jvmtiError err, const char* msg) {