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