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  * @enablePreview
 30  * @library /test/lib /test/hotspot/jtreg
 31  * @build jdk.test.whitebox.WhiteBox
 32  *
 33  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 34  * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -agentlib:ToggleNotifyJvmtiTest ToggleNotifyJvmtiTest
 35  * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -Djdk.attach.allowAttachSelf=true ToggleNotifyJvmtiTest attach
 36  */
 37 
 38 import com.sun.tools.attach.VirtualMachine;
 39 import java.util.concurrent.ThreadFactory;
 40 import jdk.test.whitebox.WhiteBox;
 41 
 42 // The TestTask mimics some thread activity, but it is important
 43 // to have sleep() calls to provide yielding as some frequency of virtual
 44 // thread mount state transitions is needed for this test scenario.
 45 class TestTask implements Runnable {
 46     private String name;
 47     private volatile boolean threadReady = false;
 48     private volatile boolean shouldFinish = false;
 49 
 50     // make thread with specific name
 51     public TestTask(String name) {
 52         this.name = name;
 53     }
 54 
 55     // run thread continuously
 56     public void run() {
 57         // run in a loop
 58         threadReady = true;
 59         System.out.println("# Started: " + name);
 60 
 61         int i = 0;
 62         int n = 1000;
 63         while (!shouldFinish) {
 64             if (n <= 0) {
 65                 n = 1000;
 66                 ToggleNotifyJvmtiTest.sleep(1);
 67             }
 68             if (i > n) {
 69                 i = 0;
 70                 n = n - 1;
 71             }
 72             i = i + 1;
 73         }
 74     }
 75 
 76     // ensure thread is ready
 77     public void ensureReady() {
 78         while (!threadReady) {
 79             ToggleNotifyJvmtiTest.sleep(1);
 80         }
 81     }
 82 
 83     public void letFinish() {
 84         shouldFinish = true;
 85     }
 86 }
 87 
 88 /*
 89  * The testing scenario consists of a number of serialized test cycles.
 90  * Each cycle has initially zero virtual threads and the following steps:
 91  *  - disable notifyJvmti events mode
 92  *  - start N virtual threads
 93  *  - enable notifyJvmti events mode
 94  *  - shut the virtual threads down
 95  * The JVMTI agent is loaded at a start-up or at a dynamic attach.
 96  * It collects events:
 97  *  - VirtualThreadStart, VirtualThreadEnd, ThreadStart and ThreadEnd
 98  */
 99 public class ToggleNotifyJvmtiTest {
100     private static final int VTHREADS_CNT = 20;
101     private static final String AGENT_LIB = "ToggleNotifyJvmtiTest";
102     private static final WhiteBox WB = WhiteBox.getWhiteBox();
103 
104     private static native boolean IsAgentStarted();
105     private static native int VirtualThreadStartedCount();
106     private static native int VirtualThreadEndedCount();
107     private static native int ThreadStartedCount();
108     private static native int ThreadEndedCount();
109 
110     static void log(String str) { System.out.println(str); }
111 
112     static public void sleep(long millis) {
113         try {
114             Thread.sleep(millis);
115         } catch (InterruptedException e) {
116             throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
117         }
118     }
119 
120     static TestTask[] tasks = new TestTask[VTHREADS_CNT];
121     static Thread vthreads[] = new Thread[VTHREADS_CNT];
122 
123     static private void startVirtualThreads() {
124         log("\n# Java: Starting vthreads");
125         for (int i = 0; i < VTHREADS_CNT; i++) {
126             String name = "TestTask" + i;
127             TestTask task = new TestTask(name);
128             vthreads[i] = Thread.ofVirtual().name(name).start(task);
129             tasks[i] = task;
130         }
131     }
132 
133     static private void finishVirtualThreads() {
134         try {
135             for (int i = 0; i < VTHREADS_CNT; i++) {
136                 tasks[i].ensureReady();
137                 tasks[i].letFinish();
138                 vthreads[i].join();
139             }
140         } catch (InterruptedException e) {
141             throw new RuntimeException(e);
142         }
143     }
144 
145     static private void setVirtualThreadsNotifyJvmtiMode(int iter, boolean enable) {
146         boolean status = WB.setVirtualThreadsNotifyJvmtiMode(enable);
147         if (!status) {
148             throw new RuntimeException("Java: failed to set VirtualThreadsNotifyJvmtiMode: " + enable);
149         }
150         log("\n# main: SetNotifyJvmtiEvents: #" + iter + " enable: " + enable);
151     }
152 
153     // Accumulative results after each finished test cycle.
154     static private void printResults() {
155         log("  VirtualThreadStart events: " + VirtualThreadStartedCount());
156         log("  VirtualThreadEnd events:   " + VirtualThreadEndedCount());
157         log("  ThreadStart events:        " + ThreadStartedCount());
158         log("  ThreadEnd events:          " + ThreadEndedCount());
159     }
160 
161     static private void run_test_cycle(int iter) throws Exception {
162         log("\n# Java: Started test cycle #" + iter);
163 
164         // Disable notifyJvmti events mode at test cycle start.
165         // It is unsafe to do so if any virtual threads are executed.
166         setVirtualThreadsNotifyJvmtiMode(iter, false);
167 
168         startVirtualThreads();
169 
170         // We want this somewhere in the middle of virtual threads execution.
171         setVirtualThreadsNotifyJvmtiMode(iter, true);
172 
173         finishVirtualThreads();
174 
175         log("\n# Java: Finished test cycle #" + iter);
176         printResults();
177     }
178 
179     public static void main(String[] args) throws Exception {
180         log("# main: loading " + AGENT_LIB + " lib");
181 
182         if (args.length > 0 && args[0].equals("attach")) { // agent loaded into running VM case
183             String arg = args.length == 2 ? args[1] : "";
184             VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
185             vm.loadAgentLibrary(AGENT_LIB, arg);
186         }
187         int waitCount = 0;
188         while (!IsAgentStarted()) {
189             log("# main: waiting for native agent to start: #" + waitCount++);
190             sleep(20);
191         }
192 
193         // The testing scenario consists of a number of sequential testing cycles.
194         for (int iter = 0; iter < 10; iter++) {
195             run_test_cycle(iter);
196         }
197     }
198 }