/* * 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 * @summary Verifies JVMTI works for agents loaded into running VM * @requires vm.jvmti * @requires vm.continuations * @enablePreview * @library /test/lib /test/hotspot/jtreg * @build jdk.test.whitebox.WhiteBox * * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm/native -XX:+WhiteBoxAPI -Xbootclasspath/a:. -agentlib:ToggleNotifyJvmtiTest ToggleNotifyJvmtiTest * @run main/othervm/native -XX:+WhiteBoxAPI -Xbootclasspath/a:. -Djdk.attach.allowAttachSelf=true ToggleNotifyJvmtiTest attach */ import com.sun.tools.attach.VirtualMachine; import java.util.concurrent.ThreadFactory; import jdk.test.whitebox.WhiteBox; // The TestTask mimics some thread activity, but it is important // to have sleep() calls to provide yielding as some frequency of virtual // thread mount state transitions is needed for this test scenario. class TestTask implements Runnable { private String name; private volatile boolean threadReady = false; private volatile boolean shouldFinish = false; // make thread with specific name public TestTask(String name) { this.name = name; } // run thread continuously public void run() { // run in a loop threadReady = true; System.out.println("# Started: " + name); int i = 0; int n = 1000; while (!shouldFinish) { if (n <= 0) { n = 1000; ToggleNotifyJvmtiTest.sleep(1); } if (i > n) { i = 0; n = n - 1; } i = i + 1; } } // ensure thread is ready public void ensureReady() { while (!threadReady) { ToggleNotifyJvmtiTest.sleep(1); } } public void letFinish() { shouldFinish = true; } } /* * The testing scenario consists of a number of serialized test cycles. * Each cycle has initially zero virtual threads and the following steps: * - disable notifyJvmti events mode * - start N virtual threads * - enable notifyJvmti events mode * - shut the virtual threads down * The JVMTI agent is loaded at a start-up or at a dynamic attach. * It collects events: * - VirtualThreadStart, VirtualThreadEnd, ThreadStart and ThreadEnd */ public class ToggleNotifyJvmtiTest { private static final int VTHREADS_CNT = 20; private static final String AGENT_LIB = "ToggleNotifyJvmtiTest"; private static final WhiteBox WB = WhiteBox.getWhiteBox(); private static native boolean IsAgentStarted(); private static native int VirtualThreadStartedCount(); private static native int VirtualThreadEndedCount(); private static native int ThreadStartedCount(); private static native int ThreadEndedCount(); static void log(String str) { System.out.println(str); } static public void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e); } } static TestTask[] tasks = new TestTask[VTHREADS_CNT]; static Thread vthreads[] = new Thread[VTHREADS_CNT]; static private void startVirtualThreads() { log("\n# Java: Starting vthreads"); for (int i = 0; i < VTHREADS_CNT; i++) { String name = "TestTask" + i; TestTask task = new TestTask(name); vthreads[i] = Thread.ofVirtual().name(name).start(task); tasks[i] = task; } } static private void finishVirtualThreads() { try { for (int i = 0; i < VTHREADS_CNT; i++) { tasks[i].ensureReady(); tasks[i].letFinish(); vthreads[i].join(); } } catch (InterruptedException e) { throw new RuntimeException(e); } } static private void setVirtualThreadsNotifyJvmtiMode(int iter, boolean enable) { boolean status = WB.setVirtualThreadsNotifyJvmtiMode(enable); if (!status) { throw new RuntimeException("Java: failed to set VirtualThreadsNotifyJvmtiMode: " + enable); } log("\n# main: SetNotifyJvmtiEvents: #" + iter + " enable: " + enable); } // Accumulative results after each finished test cycle. static private void printResults() { log(" VirtualThreadStart events: " + VirtualThreadStartedCount()); log(" VirtualThreadEnd events: " + VirtualThreadEndedCount()); log(" ThreadStart events: " + ThreadStartedCount()); log(" ThreadEnd events: " + ThreadEndedCount()); } static private void run_test_cycle(int iter) throws Exception { log("\n# Java: Started test cycle #" + iter); // Disable notifyJvmti events mode at test cycle start. // It is unsafe to do so if any virtual threads are executed. setVirtualThreadsNotifyJvmtiMode(iter, false); startVirtualThreads(); // We want this somewhere in the middle of virtual threads execution. setVirtualThreadsNotifyJvmtiMode(iter, true); finishVirtualThreads(); log("\n# Java: Finished test cycle #" + iter); printResults(); } public static void main(String[] args) throws Exception { log("# main: loading " + AGENT_LIB + " lib"); if (args.length > 0 && args[0].equals("attach")) { // agent loaded into running VM case String arg = args.length == 2 ? args[1] : ""; VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid())); vm.loadAgentLibrary(AGENT_LIB, arg); } int waitCount = 0; while (!IsAgentStarted()) { log("# main: waiting for native agent to start: #" + waitCount++); sleep(20); } // The testing scenario consists of a number of sequential testing cycles. for (int iter = 0; iter < 10; iter++) { run_test_cycle(iter); } } }