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 id=default
 26  * @summary Verifies JVMTI StopThread support for virtual threads.
 27  * @requires vm.continuations
 28  * @library /test/lib
 29  * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest
 30  */
 31 
 32 /*
 33  * @test id=no-vmcontinuations
 34  * @summary Verifies JVMTI StopThread support for bound virtual threads.
 35  * @library /test/lib
 36  * @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations -DboundVThread=true StopThreadTest
 37  */
 38 
 39 /*
 40  * @test id=platform
 41  * @summary Verifies JVMTI StopThread support for platform threads.
 42  * @library /test/lib
 43  * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest platform
 44  */
 45 
 46 import jdk.test.lib.Platform;
 47 import java.lang.AssertionError;
 48 
 49 /*
 50  *     The test exercises the JVMTI function: StopThread(jthread).
 51  *     The test creates a new virtual or platform thread.
 52  *     Its method run() invokes the following methods:
 53  *      - method A() that is blocked on a monitor
 54  *      - method B() that is stopped at a breakpoint
 55  *      - method C() that forces agent to send AssertionError exception to its own thread
 56  *     All cases are using JVMTI StopThread to send an AssertionError object.
 57  */
 58 public class StopThreadTest {
 59     private static final String agentLib = "StopThreadTest";
 60     static final boolean isBoundVThread = Boolean.getBoolean("boundVThread");
 61     static final int JVMTI_ERROR_NONE = 0;
 62     static final int JVMTI_ERROR_OPAQUE_FRAME = 32;
 63     static final int THREAD_NOT_SUSPENDED = 13;
 64     static final int PASSED = 0;
 65     static final int FAILED = 2;
 66 
 67     static void log(String str) { System.out.println(str); }
 68 
 69     static native void prepareAgent(Class taskClass, Object exceptionObject);
 70     static native void suspendThread(Thread thread);
 71     static native void resumeThread(Thread thread);
 72     static native void ensureAtBreakpoint();
 73     static native void notifyAtBreakpoint();
 74     static native int  stopThread(Thread thread);
 75 
 76     static int status = PASSED;
 77     static boolean is_virtual = true;
 78 
 79     static void setFailed(String msg) {
 80         log("\nFAILED: " + msg);
 81         status = FAILED;
 82     }
 83 
 84     static void throwFailed(String msg) {
 85         log("\nFAILED: " + msg);
 86         throw new RuntimeException("StopThreadTest failed!");
 87     }
 88 
 89     public static void main(String args[]) {
 90         is_virtual = !(args.length > 0 && args[0].equals("platform"));
 91         run();
 92         if (status == FAILED) {
 93             throwFailed("StopThreadTest!");
 94         }
 95         log("\nStopThreadTest passed");
 96     }
 97 
 98     public static void run() {
 99         TestTask testTask = new TestTask();
100         Thread testTaskThread = null;
101         AssertionError excObject = new AssertionError();
102         int retCode;
103 
104         prepareAgent(TestTask.class, excObject);
105 
106         log("\nMain #A: method A() must be blocked on entering a synchronized statement");
107         synchronized (TestTask.lock) {
108             if (is_virtual) {
109                 testTaskThread = Thread.ofVirtual().name("TestTaskThread").start(testTask);
110             } else {
111                 testTaskThread = Thread.ofPlatform().name("TestTaskThread").start(testTask);
112             }
113             TestTask.ensureAtPointA(testTaskThread);
114 
115             if (is_virtual) { // this check is for virtual target thread only
116                 log("\nMain #A.1: unsuspended");
117                 retCode = stopThread(testTaskThread);
118                 if (retCode != THREAD_NOT_SUSPENDED) {
119                     throwFailed("Main #A.1: expected THREAD_NOT_SUSPENDED instead of: " + retCode);
120                 } else {
121                     log("Main #A.1: got expected THREAD_NOT_SUSPENDED");
122                 }
123             }
124 
125             log("\nMain #A.2: suspended");
126             suspendThread(testTaskThread);
127             retCode = stopThread(testTaskThread);
128             int expectedRetCode = (is_virtual && !isBoundVThread && (Platform.isX64() || Platform.isAArch64())) ? JVMTI_ERROR_OPAQUE_FRAME : JVMTI_ERROR_NONE;
129             String expectedRetCodeName = (is_virtual && !isBoundVThread && (Platform.isX64() || Platform.isAArch64())) ?
130                 "JVMTI_ERROR_OPAQUE_FRAME" : "JVMTI_ERROR_NONE";
131             if (retCode != expectedRetCode) {
132                 throwFailed("Main #A.2: expected " + expectedRetCodeName + " instead of: " + retCode);
133             } else {
134                 log("Main #A.2: got expected " + expectedRetCodeName);
135             }
136             resumeThread(testTaskThread);
137         }
138         log("\nMain #B: method B() must be blocked in a breakpoint event handler");
139         {
140             ensureAtBreakpoint();
141 
142             if (is_virtual) { // this check is for virtual target thread only
143                 log("\nMain #B.1: unsuspended");
144                 retCode = stopThread(testTaskThread);
145                 if (retCode != THREAD_NOT_SUSPENDED) {
146                     throwFailed("Main #B.1: expected THREAD_NOT_SUSPENDED instead of: " + retCode);
147                 }
148             }
149 
150             log("\nMain #B.2: suspended");
151             suspendThread(testTaskThread);
152             retCode = stopThread(testTaskThread);
153             if (retCode != JVMTI_ERROR_NONE) {
154                 throwFailed("Main #B.2: expected JVMTI_ERROR_NONE");
155             }
156             resumeThread(testTaskThread);
157 
158             notifyAtBreakpoint();
159         }
160 
161         log("\nMain #C: method C() sends AssertionError object to its own thread");
162         {
163             // StopThread is called from the test task (own thread) and expected to succeed.
164             // No suspension of the test task thread is required or can be done in this case.
165             TestTask.ensureFinished();
166         }
167 
168         try {
169             testTaskThread.join();
170         } catch (InterruptedException ex) {
171             throwFailed("Unexpected " + ex);
172         }
173     }
174 
175 
176     static class TestTask implements Runnable {
177         static Object lock = new Object();
178         static void log(String str) { System.out.println(str); }
179 
180         static volatile boolean atPointA = false;
181         static volatile boolean finished = false;
182 
183         static void sleep(long millis) {
184             try {
185                 Thread.sleep(millis);
186             } catch (InterruptedException e) {
187                 throw new RuntimeException("Interruption in TestTask.sleep: \n\t" + e);
188             }
189         }
190 
191         static void ensureAtPointA(Thread vt) {
192             // wait while the thread state is not the expected one
193             while (vt.getState() != Thread.State.BLOCKED) {
194                 sleep(1);
195             }
196         }
197 
198         // Ensure thread is finished.
199         static void ensureFinished() {
200             while (!finished) {
201                 sleep(1);
202             }
203         }
204 
205         public void run() {
206             log("TestTask.run: started");
207 
208             boolean seenExceptionFromA = false;
209             try {
210                 A();
211             } catch (AssertionError ex) {
212                 log("TestTask.run: caught expected AssertionError from method A()");
213                 seenExceptionFromA = true;
214             }
215             Thread.interrupted();
216             if (!seenExceptionFromA
217                 && !(is_virtual
218                      && !isBoundVThread
219                      && (Platform.isX64() || (Platform.isAArch64())) )) {
220                 StopThreadTest.setFailed("TestTask.run: expected AssertionError from method A()");
221             }
222             sleep(1); // to cause yield
223 
224             boolean seenExceptionFromB = false;
225             try {
226               B();
227             } catch (AssertionError ex) {
228                 log("TestTask.run: caught expected AssertionError from method B()");
229                 seenExceptionFromB = true;
230             }
231             Thread.interrupted();
232             if (!seenExceptionFromB) {
233                 StopThreadTest.setFailed("TestTask.run: expected AssertionError from method B()");
234             }
235             sleep(1); // to cause yield
236 
237             boolean seenExceptionFromC = false;
238             try {
239                 C();
240             } catch (AssertionError ex) {
241                 log("TestTask.run: caught expected AssertionError from method C()");
242                 seenExceptionFromC = true;
243             }
244             Thread.interrupted();
245             if (!seenExceptionFromC) {
246                 StopThreadTest.setFailed("TestTask.run: expected AssertionError from method C()");
247             }
248             finished = true;
249         }
250 
251         // Method is blocked on entering a synchronized statement.
252         // StopThread is used to send an AssertionError object two times:
253         //  - when not suspended: THREAD_NOT_SUSPENDED is expected
254         //  - when suspended: JVMTI_ERROR_NONE is expected
255         static void A() {
256             log("TestTask.A: started");
257             synchronized (lock) {
258             }
259             log("TestTask.A: finished");
260         }
261 
262         // A breakpoint is set at start of this method.
263         // StopThread is used to send an AssertionError object two times:
264         //  - when not suspended: THREAD_NOT_SUSPENDED is expected
265         //  - when suspended: expected to succeed
266         static void B() {
267             log("TestTask.B: started");
268         }
269 
270         // This method uses StopThread to send an AssertionError object to
271         // its own thread. It is expected to succeed.
272         static void C() {
273             log("TestTask.C: started");
274             StopThreadTest.stopThread(Thread.currentThread());
275             log("TestTask.C: finished");
276         }
277     }
278 }