1 /*
2 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /**
25 * @test
26 * @summary Verifies JVMTI works for agents loaded into running VM
27 * @requires vm.jvmti
28 * @requires vm.continuations
29 * @library /test/lib /test/hotspot/jtreg
30 * @build jdk.test.whitebox.WhiteBox
31 *
32 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
33 * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -agentlib:ToggleNotifyJvmtiTest ToggleNotifyJvmtiTest
34 * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -Djdk.attach.allowAttachSelf=true ToggleNotifyJvmtiTest attach
35 */
36
37 import com.sun.tools.attach.VirtualMachine;
38 import java.util.concurrent.ThreadFactory;
39 import jdk.test.whitebox.WhiteBox;
40
41 // The TestTask mimics some thread activity, but it is important
42 // to have sleep() calls to provide yielding as some frequency of virtual
43 // thread mount state transitions is needed for this test scenario.
44 class TestTask implements Runnable {
45 private String name;
46 private volatile boolean threadReady = false;
47 private volatile boolean shouldFinish = false;
48
49 // make thread with specific name
50 public TestTask(String name) {
51 this.name = name;
52 }
53
54 // run thread continuously
55 public void run() {
56 // run in a loop
57 threadReady = true;
58 System.out.println("# Started: " + name);
59
60 int i = 0;
61 int n = 1000;
62 while (!shouldFinish) {
63 if (n <= 0) {
64 n = 1000;
65 ToggleNotifyJvmtiTest.sleep(1);
66 }
67 if (i > n) {
68 i = 0;
69 n = n - 1;
70 }
71 i = i + 1;
72 }
73 }
74
75 // ensure thread is ready
76 public void ensureReady() {
77 while (!threadReady) {
78 ToggleNotifyJvmtiTest.sleep(1);
79 }
80 }
81
82 public void letFinish() {
83 shouldFinish = true;
84 }
85 }
86
87 /*
88 * The testing scenario consists of a number of serialized test cycles.
89 * Each cycle has initially zero virtual threads and the following steps:
90 * - disable notifyJvmti events mode
91 * - start N virtual threads
92 * - enable notifyJvmti events mode
93 * - shut the virtual threads down
94 * The JVMTI agent is loaded at a start-up or at a dynamic attach.
95 * It collects events:
96 * - VirtualThreadStart, VirtualThreadEnd, ThreadStart and ThreadEnd
97 */
98 public class ToggleNotifyJvmtiTest {
99 private static final int VTHREADS_CNT = 20;
100 private static final String AGENT_LIB = "ToggleNotifyJvmtiTest";
101 private static final WhiteBox WB = WhiteBox.getWhiteBox();
102
103 private static native boolean IsAgentStarted();
104 private static native int VirtualThreadStartedCount();
105 private static native int VirtualThreadEndedCount();
106 private static native int ThreadStartedCount();
107 private static native int ThreadEndedCount();
108
109 static void log(String str) { System.out.println(str); }
110
111 static public void sleep(long millis) {
112 try {
113 Thread.sleep(millis);
114 } catch (InterruptedException e) {
115 throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
116 }
117 }
118
119 static TestTask[] tasks = new TestTask[VTHREADS_CNT];
120 static Thread vthreads[] = new Thread[VTHREADS_CNT];
121
122 static private void startVirtualThreads() {
123 log("\n# Java: Starting vthreads");
124 for (int i = 0; i < VTHREADS_CNT; i++) {
125 String name = "TestTask" + i;
126 TestTask task = new TestTask(name);
127 vthreads[i] = Thread.ofVirtual().name(name).start(task);
128 tasks[i] = task;
129 }
130 }
131
132 static private void finishVirtualThreads() {
133 try {
134 for (int i = 0; i < VTHREADS_CNT; i++) {
135 tasks[i].ensureReady();
136 tasks[i].letFinish();
137 vthreads[i].join();
138 }
139 } catch (InterruptedException e) {
140 throw new RuntimeException(e);
141 }
142 }
143
144 static private void setVirtualThreadsNotifyJvmtiMode(int iter, boolean enable) {
145 boolean status = WB.setVirtualThreadsNotifyJvmtiMode(enable);
146 if (!status) {
147 throw new RuntimeException("Java: failed to set VirtualThreadsNotifyJvmtiMode: " + enable);
148 }
149 log("\n# main: SetNotifyJvmtiEvents: #" + iter + " enable: " + enable);
150 }
151
152 // Accumulative results after each finished test cycle.
153 static private void printResults() {
154 log(" VirtualThreadStart events: " + VirtualThreadStartedCount());
155 log(" VirtualThreadEnd events: " + VirtualThreadEndedCount());
156 log(" ThreadStart events: " + ThreadStartedCount());
157 log(" ThreadEnd events: " + ThreadEndedCount());
158 }
159
160 static private void run_test_cycle(int iter) throws Exception {
161 log("\n# Java: Started test cycle #" + iter);
162
163 // Disable notifyJvmti events mode at test cycle start.
164 // It is unsafe to do so if any virtual threads are executed.
165 setVirtualThreadsNotifyJvmtiMode(iter, false);
166
167 startVirtualThreads();
168
169 // We want this somewhere in the middle of virtual threads execution.
170 setVirtualThreadsNotifyJvmtiMode(iter, true);
171
172 finishVirtualThreads();
173
174 log("\n# Java: Finished test cycle #" + iter);
175 printResults();
176 }
177
178 public static void main(String[] args) throws Exception {
179 log("# main: loading " + AGENT_LIB + " lib");
180
181 if (args.length > 0 && args[0].equals("attach")) { // agent loaded into running VM case
182 String arg = args.length == 2 ? args[1] : "";
183 VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
184 vm.loadAgentLibrary(AGENT_LIB, arg);
185 }
186 int waitCount = 0;
187 while (!IsAgentStarted()) {
188 log("# main: waiting for native agent to start: #" + waitCount++);
189 sleep(20);
190 }
191
192 // The testing scenario consists of a number of sequential testing cycles.
193 for (int iter = 0; iter < 10; iter++) {
194 run_test_cycle(iter);
195 }
196 }
197 }