1 /*
  2  * Copyright (c) 2019, 2021, 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  * @bug 8231595
 27  * @summary [TEST] develop a test case for SuspendThreadList including current thread
 28  * @requires vm.jvmti
 29  * @library /test/lib
 30  * @compile SuspendWithCurrentThread.java
 31  * @run main/othervm/native -agentlib:SuspendWithCurrentThread SuspendWithCurrentThread SuspenderIndex=first
 32  * @run main/othervm/native -agentlib:SuspendWithCurrentThread SuspendWithCurrentThread SuspenderIndex=last
 33  */
 34 
 35 import java.io.PrintStream;
 36 
 37 public class SuspendWithCurrentThread {
 38     private static final String AGENT_LIB = "SuspendWithCurrentThread";
 39     private static final String SUSPENDER_OPT = "SuspenderIndex=";
 40     private static final int THREADS_COUNT = 10;
 41 
 42     private static void log(String msg) { System.out.println(msg); }
 43 
 44     private static native void    registerTestedThreads(Thread[] threads);
 45     private static native boolean checkTestedThreadsSuspended();
 46     private static native void    resumeTestedThreads();
 47     private static native void    releaseTestedThreadsInfo();
 48 
 49     // The suspender thread index defines the thread which has to suspend
 50     // the tested threads including itself with the JVMTI SuspendThreadList
 51     private static int suspenderIndex;
 52 
 53     public static void main(String args[]) throws Exception {
 54         try {
 55             System.loadLibrary(AGENT_LIB);
 56             log("Loaded library: " + AGENT_LIB);
 57         } catch (UnsatisfiedLinkError ule) {
 58             log("Failed to load library: " + AGENT_LIB);
 59             log("java.library.path: " + System.getProperty("java.library.path"));
 60             throw ule;
 61         }
 62         if (args.length != 1) {
 63             throw new RuntimeException("Main: wrong arguments count: " + args.length + ", expected: 1");
 64         }
 65         String arg = args[0];
 66         if (arg.equals(SUSPENDER_OPT + "first")) {
 67             suspenderIndex = 0;
 68         } else if (arg.equals(SUSPENDER_OPT + "last")) {
 69             suspenderIndex = THREADS_COUNT - 1;
 70         } else {
 71             throw new RuntimeException("Main: wrong argument: " + arg + ", expected: SuspenderIndex={first|last}");
 72         }
 73         log("Main: suspenderIndex: " + suspenderIndex);
 74 
 75         SuspendWithCurrentThread test = new SuspendWithCurrentThread();
 76         test.run();
 77     }
 78 
 79     private ThreadToSuspend[] startTestedThreads(int threadsCount) throws RuntimeException  {
 80         ThreadToSuspend[]threads = new ThreadToSuspend[threadsCount];
 81 
 82         // create tested threads
 83         for (int i = 0; i < threads.length; i++) {
 84             threads[i] = new ThreadToSuspend("ThreadToSuspend#" + i,
 85                                              i == suspenderIndex // isSuspender
 86                                             );
 87         }
 88         log("Main: starting tested threads");
 89         for (int i = 0; i < threads.length; i++) {
 90             threads[i].start();
 91             if (!threads[i].checkReady()) {
 92                 throw new RuntimeException("Main: unable to prepare tested thread: " + threads[i]);
 93             }
 94         }
 95         log("Main: tested threads started");
 96 
 97         registerTestedThreads(threads);
 98         return threads;
 99     }
100 
101     private boolean checkSuspendedStatus() throws RuntimeException  {
102         log("Main: checking all tested threads have been suspended");
103         return checkTestedThreadsSuspended();
104     }
105 
106     /* The test does the following steps:
107      *  - main thread starts several (THREADS_COUNT) ThreadToSuspend tested threads
108      *  - main thread waits for threads to be ready with the thread.checkReady()
109      *  - main thread registers tested threads within the native agent library
110      *    with the native method registerTestedThreads()
111      *  - main thread triggers the suspender tested thread with the
112      *    ThreadToSuspend.setAllThreadsReady() to suspend tested threads
113      *  - suspender thread suspends tested threads including itself with the native
114      *    method suspendTestedThreads() (uses the JVMTI SuspendThreadList function)
115      *  - main thread checks tested threads suspended status with the native method
116      *    checkSuspendedStatus(); the tested threads are expected to have suspended status
117      *  - main thread resumes tested threads with the native method resumeTestedThreads()
118      *  - main thread releases tested threads with the native method releaseTestedThreads()
119      *  - main thread triggers the tested threads to finish with the thread.letFinish()
120      */
121     private void run() throws Exception {
122         ThreadToSuspend[] threads = null; // tested threads
123 
124         log("Main: started");
125         try {
126             threads = startTestedThreads(THREADS_COUNT);
127 
128             log("Main: trigger " + threads[suspenderIndex].getName() +
129                 " to suspend all tested threads including itself");
130             ThreadToSuspend.setAllThreadsReady();
131 
132             while (!checkSuspendedStatus()) {
133                 Thread.sleep(10);
134             }
135 
136             log("Main: resuming all tested threads");
137             resumeTestedThreads();
138         } finally {
139             // let threads to finish
140             for (int i = 0; i < threads.length; i++) {
141                 threads[i].letFinish();
142             }
143             log("Main: tested threads finished");
144         }
145 
146         // wait for threads to finish
147         log("Main: joining tested threads");
148         try {
149             for (int i = 0; i < threads.length; i++) {
150                 threads[i].join();
151             }
152             log("Main: tested thread joined");
153         } catch (InterruptedException e) {
154             throw new RuntimeException(e);
155         }
156         log("Main: releasing tested threads native info");
157         releaseTestedThreadsInfo();
158 
159         log("Main: finished");
160     }
161 }
162 
163 /* =================================================================== */
164 
165 // tested threads
166 class ThreadToSuspend extends Thread {
167     private static void log(String msg) { System.out.println(msg); }
168 
169     private static native void suspendTestedThreads();
170     private static volatile boolean allThreadsReady = false;
171 
172     public static void setAllThreadsReady() {
173         allThreadsReady = true;
174     }
175 
176     private volatile boolean threadReady = false;
177     private volatile boolean shouldFinish = false;
178     private boolean isSuspender = false;
179 
180     // make thread with specific name
181     public ThreadToSuspend(String name, boolean isSuspender) {
182         super(name);
183         this.isSuspender = isSuspender;
184     }
185 
186     // run thread continuously
187     public void run() {
188         boolean needSuspend = true;
189         threadReady = true;
190 
191         // run in a loop
192         while (!shouldFinish) {
193             if (isSuspender && needSuspend && allThreadsReady) {
194                 log(getName() + ": before suspending all tested threads including myself");
195                 needSuspend = false;
196                 suspendTestedThreads();
197                 log(getName() + ": after suspending all tested threads including myself");
198             }
199         }
200     }
201 
202     // check if thread is ready
203     public boolean checkReady() {
204         try {
205             while (!threadReady) {
206                 sleep(1);
207             }
208         } catch (InterruptedException e) {
209             throw new RuntimeException("checkReady: sleep was interrupted\n\t" + e);
210         }
211         return threadReady;
212     }
213 
214     // let thread to finish
215     public void letFinish() {
216         shouldFinish = true;
217     }
218 }