1 /*
2  * Copyright (c) 2001, 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 4413752
27  * @summary Test SuspendThread with RawMonitor enter.
28  * @requires vm.jvmti
29  * @library /test/lib
30  * @compile SuspendWithRawMonitorEnter.java
31  * @run main/othervm/native -agentlib:SuspendWithRawMonitorEnter SuspendWithRawMonitorEnter
32  */
33 
34 import java.io.PrintStream;
35 
36 //
37 // main               blocker           contender            resumer
38 // =================  ================  ===================  ================
39 // launch blocker
40 // <launch returns>   blocker running
41 // launch contender   enter threadLock
42 // <launch returns>   wait for notify   contender running
43 // launch resumer     :                 block on threadLock
44 // <launch returns>   :                 :                    resumer running
45 // suspend contender  :                 <suspended>          wait for notify
46 // <ready to test>    :                 :                    :
47 // :                  :                 :                    :
48 // notify blocker     wait finishes     :                    :
49 // notify resumer     exit threadLock   :                    wait finishes
50 // join blocker       :                 :                    enter threadLock
51 // <join returns>     blocker exits     <resumed>            resume contender
52 // join resumer                         :                    exit threadLock
53 // <join returns>                       enter threadLock     resumer exits
54 // join contender                       exit threadLock
55 // <join returns>                       contender exits
56 //
57 
58 public class SuspendWithRawMonitorEnter {
59     private static final String AGENT_LIB = "SuspendWithRawMonitorEnter";
60     private static final int exit_delta   = 95;
61 
62     private static final int DEF_TIME_MAX = 60;    // default max # secs to test
63     private static final int JOIN_MAX     = 30;    // max # secs to wait for join
64 
65     public static final int TS_INIT              = 1;  // initial testState
66     public static final int TS_BLOCKER_RUNNING   = 2;  // blocker is running
67     public static final int TS_CONTENDER_RUNNING = 3;  // contender is running
68     public static final int TS_RESUMER_RUNNING   = 4;  // resumer is running
69     public static final int TS_CALL_SUSPEND      = 5;  // call suspend on contender
70     public static final int TS_DONE_BLOCKING     = 6;  // done blocking threadLock
71     public static final int TS_READY_TO_RESUME   = 7;  // ready to resume contender
72     public static final int TS_CALL_RESUME       = 8;  // call resume on contender
73     public static final int TS_CONTENDER_DONE    = 9;  // contender has run; done
74 
75     public static Object barrierLaunch = new Object();   // controls thread launch
76     public static Object barrierBlocker = new Object();  // controls blocker
77     public static Object barrierResumer = new Object();  // controls resumer
78 
79     public static long count = 0;
80     public static boolean printDebug = false;
81     public volatile static int testState;
82 
log(String msg)83     private static void log(String msg) { System.out.println(msg); }
84 
createRawMonitor()85     native static int createRawMonitor();
destroyRawMonitor()86     native static int destroyRawMonitor();
suspendThread(SuspendWithRawMonitorEnterWorker thr)87     native static int suspendThread(SuspendWithRawMonitorEnterWorker thr);
88 
main(String[] args)89     public static void main(String[] args) throws Exception {
90         try {
91             System.loadLibrary(AGENT_LIB);
92             log("Loaded library: " + AGENT_LIB);
93         } catch (UnsatisfiedLinkError ule) {
94             log("Failed to load library: " + AGENT_LIB);
95             log("java.library.path: " + System.getProperty("java.library.path"));
96             throw ule;
97         }
98 
99         int timeMax = 0;
100         if (args.length == 0) {
101             timeMax = DEF_TIME_MAX;
102         } else {
103             int argIndex = 0;
104             int argsLeft = args.length;
105             if (args[0].equals("-p")) {
106                 printDebug = true;
107                 argIndex = 1;
108                 argsLeft--;
109             }
110             if (argsLeft == 0) {
111                 timeMax = DEF_TIME_MAX;
112             } else if (argsLeft == 1) {
113                 try {
114                     timeMax = Integer.parseUnsignedInt(args[argIndex]);
115                 } catch (NumberFormatException nfe) {
116                     System.err.println("'" + args[argIndex] +
117                                        "': invalid timeMax value.");
118                     usage();
119                 }
120             } else {
121                 usage();
122             }
123         }
124 
125         System.exit(run(timeMax, System.out) + exit_delta);
126     }
127 
logDebug(String mesg)128     public static void logDebug(String mesg) {
129         if (printDebug) {
130             System.err.println(Thread.currentThread().getName() + ": " + mesg);
131         }
132     }
133 
usage()134     public static void usage() {
135         System.err.println("Usage: " + AGENT_LIB + " [-p][time_max]");
136         System.err.println("where:");
137         System.err.println("    -p       ::= print debug info");
138         System.err.println("    time_max ::= max looping time in seconds");
139         System.err.println("                 (default is " + DEF_TIME_MAX +
140                            " seconds)");
141         System.exit(1);
142     }
143 
run(int timeMax, PrintStream out)144     public static int run(int timeMax, PrintStream out) {
145         return (new SuspendWithRawMonitorEnter()).doWork(timeMax, out);
146     }
147 
checkTestState(int exp)148     public static void checkTestState(int exp) {
149         if (testState != exp) {
150             System.err.println("Failure at " + count + " loops.");
151             throw new InternalError("Unexpected test state value: "
152                 + "expected=" + exp + " actual=" + testState);
153         }
154     }
155 
doWork(int timeMax, PrintStream out)156     public int doWork(int timeMax, PrintStream out) {
157         SuspendWithRawMonitorEnterWorker blocker;    // blocker thread
158         SuspendWithRawMonitorEnterWorker contender;  // contender thread
159         SuspendWithRawMonitorEnterWorker resumer;    // resumer thread
160 
161         int retCode = createRawMonitor();
162         if (retCode != 0) {
163             throw new RuntimeException("error in JVMTI CreateRawMonitor: " +
164                                        "retCode=" + retCode);
165         }
166         logDebug("created threadLock");
167 
168         System.out.println("About to execute for " + timeMax + " seconds.");
169 
170         long start_time = System.currentTimeMillis();
171         while (System.currentTimeMillis() < start_time + (timeMax * 1000)) {
172             count++;
173             testState = TS_INIT;  // starting the test loop
174 
175             // launch the blocker thread
176             synchronized (barrierLaunch) {
177                 blocker = new SuspendWithRawMonitorEnterWorker("blocker");
178                 blocker.start();
179 
180                 while (testState != TS_BLOCKER_RUNNING) {
181                     try {
182                         barrierLaunch.wait(0);  // wait until it is running
183                     } catch (InterruptedException ex) {
184                     }
185                 }
186             }
187 
188             // launch the contender thread
189             synchronized (barrierLaunch) {
190                 contender = new SuspendWithRawMonitorEnterWorker("contender");
191                 contender.start();
192 
193                 while (testState != TS_CONTENDER_RUNNING) {
194                     try {
195                         barrierLaunch.wait(0);  // wait until it is running
196                     } catch (InterruptedException ex) {
197                     }
198                 }
199             }
200 
201             // launch the resumer thread
202             synchronized (barrierLaunch) {
203                 resumer = new SuspendWithRawMonitorEnterWorker("resumer", contender);
204                 resumer.start();
205 
206                 while (testState != TS_RESUMER_RUNNING) {
207                     try {
208                         barrierLaunch.wait(0);  // wait until it is running
209                     } catch (InterruptedException ex) {
210                     }
211                 }
212             }
213 
214             //
215             // Known bug: We don't have a way of knowing when the
216             // contender thread contends on the threadLock. If we
217             // suspend it before it has blocked, then we don't really
218             // have contention. However, the resumer thread won't
219             // resume the contender thread until after it has grabbed
220             // the threadLock so we don't have a lock order problem
221             // and the test won't fall over.
222             //
223             // We reduce the size of this timing window by launching
224             // the resumer thread after the contender thread. So the
225             // contender thread has all the setup time for the resumer
226             // thread to call JVM/TI RawMonitorEnter() and block on
227             // the threadLock.
228             //
229             checkTestState(TS_RESUMER_RUNNING);
230             testState = TS_CALL_SUSPEND;
231             logDebug("before suspend thread");
232             retCode = suspendThread(contender);
233             if (retCode != 0) {
234                 throw new RuntimeException("error in JVMTI SuspendThread: " +
235                                            "retCode=" + retCode);
236             }
237             logDebug("suspended thread");
238 
239             //
240             // At this point, all of the child threads are running
241             // and we can get to meat of the test:
242             //
243             // - suspended threadLock contender
244             // - a threadLock exit in the blocker thread
245             // - a threadLock enter in the resumer thread
246             // - resumption of the contender thread
247             // - a threadLock enter in the freshly resumed contender thread
248             //
249             synchronized (barrierBlocker) {
250                 checkTestState(TS_CALL_SUSPEND);
251 
252                 // tell blocker thread to exit threadLock
253                 testState = TS_DONE_BLOCKING;
254                 barrierBlocker.notify();
255             }
256 
257             synchronized (barrierResumer) {
258                 // tell resumer thread to resume contender thread
259                 testState = TS_READY_TO_RESUME;
260                 barrierResumer.notify();
261 
262                 // Can't call checkTestState() here because the
263                 // resumer thread may have already resumed the
264                 // contender thread.
265             }
266 
267             try {
268                 blocker.join();
269                 resumer.join(JOIN_MAX * 1000);
270                 if (resumer.isAlive()) {
271                     System.err.println("Failure at " + count + " loops.");
272                     throw new InternalError("resumer thread is stuck");
273                 }
274                 contender.join(JOIN_MAX * 1000);
275                 if (contender.isAlive()) {
276                     System.err.println("Failure at " + count + " loops.");
277                     throw new InternalError("contender thread is stuck");
278                 }
279             } catch (InterruptedException ex) {
280             }
281 
282             checkTestState(TS_CONTENDER_DONE);
283         }
284         retCode = destroyRawMonitor();
285         if (retCode != 0) {
286             throw new RuntimeException("error in JVMTI DestroyRawMonitor: " +
287                                        "retCode=" + retCode);
288         }
289         logDebug("destroyed threadLock");
290 
291         System.out.println("Executed " + count + " loops in " + timeMax +
292                            " seconds.");
293 
294         return 0;
295     }
296 }
297 
298 class SuspendWithRawMonitorEnterWorker extends Thread {
299     private SuspendWithRawMonitorEnterWorker target;  // target for resume operation
300 
SuspendWithRawMonitorEnterWorker(String name)301     public SuspendWithRawMonitorEnterWorker(String name) {
302         super(name);
303     }
304 
SuspendWithRawMonitorEnterWorker(String name, SuspendWithRawMonitorEnterWorker target)305     public SuspendWithRawMonitorEnterWorker(String name, SuspendWithRawMonitorEnterWorker target) {
306         super(name);
307         this.target = target;
308     }
309 
rawMonitorEnter()310     native static int rawMonitorEnter();
rawMonitorExit()311     native static int rawMonitorExit();
resumeThread(SuspendWithRawMonitorEnterWorker thr)312     native static int resumeThread(SuspendWithRawMonitorEnterWorker thr);
313 
run()314     public void run() {
315         SuspendWithRawMonitorEnter.logDebug("thread running");
316 
317         //
318         // Launch the blocker thread:
319         // - grabs threadLock
320         // - holds threadLock until we tell it let go
321         // - releases threadLock
322         //
323         int retCode;
324         if (getName().equals("blocker")) {
325             // grab threadLock before we tell main we are running
326             SuspendWithRawMonitorEnter.logDebug("before enter threadLock");
327             retCode = rawMonitorEnter();
328             if (retCode != 0) {
329                 throw new RuntimeException("error in JVMTI RawMonitorEnter: " +
330                                            "retCode=" + retCode);
331             }
332             SuspendWithRawMonitorEnter.logDebug("enter threadLock");
333 
334             SuspendWithRawMonitorEnter.checkTestState(SuspendWithRawMonitorEnter.TS_INIT);
335 
336             // recursive entry
337             SuspendWithRawMonitorEnter.logDebug("before recursive enter threadLock");
338             retCode = rawMonitorEnter();
339             if (retCode != 0) {
340                 throw new RuntimeException("error in JVMTI RawMonitorEnter: " +
341                                            "retCode=" + retCode);
342             }
343             SuspendWithRawMonitorEnter.logDebug("recursive enter threadLock");
344 
345             SuspendWithRawMonitorEnter.logDebug("before recursive exit threadLock");
346             retCode = rawMonitorExit();
347             if (retCode != 0) {
348                 throw new RuntimeException("error in JVMTI RawMonitorExit: " +
349                                            "retCode=" + retCode);
350             }
351             SuspendWithRawMonitorEnter.logDebug("recursive exit threadLock");
352 
353             synchronized(SuspendWithRawMonitorEnter.barrierBlocker) {
354                 synchronized(SuspendWithRawMonitorEnter.barrierLaunch) {
355                     // tell main we are running
356                     SuspendWithRawMonitorEnter.testState = SuspendWithRawMonitorEnter.TS_BLOCKER_RUNNING;
357                     SuspendWithRawMonitorEnter.barrierLaunch.notify();
358                 }
359                 SuspendWithRawMonitorEnter.logDebug("thread waiting");
360                 // TS_READY_TO_RESUME is set right after TS_DONE_BLOCKING
361                 // is set so either can get the blocker thread out of
362                 // this wait() wrapper:
363                 while (SuspendWithRawMonitorEnter.testState != SuspendWithRawMonitorEnter.TS_DONE_BLOCKING &&
364                        SuspendWithRawMonitorEnter.testState != SuspendWithRawMonitorEnter.TS_READY_TO_RESUME) {
365                     try {
366                         // wait for main to tell us when to exit threadLock
367                         SuspendWithRawMonitorEnter.barrierBlocker.wait(0);
368                     } catch (InterruptedException ex) {
369                     }
370                 }
371                 SuspendWithRawMonitorEnter.logDebug("before exit threadLock");
372                 retCode = rawMonitorExit();
373                 if (retCode != 0) {
374                     throw new RuntimeException("error in JVMTI RawMonitorExit: "
375                                                + "retCode=" + retCode);
376                 }
377                 SuspendWithRawMonitorEnter.logDebug("exit threadLock");
378             }
379         }
380         //
381         // Launch the contender thread:
382         // - tries to grab the threadLock
383         // - grabs threadLock
384         // - releases threadLock
385         //
386         else if (getName().equals("contender")) {
387             synchronized(SuspendWithRawMonitorEnter.barrierLaunch) {
388                 // tell main we are running
389                 SuspendWithRawMonitorEnter.testState = SuspendWithRawMonitorEnter.TS_CONTENDER_RUNNING;
390                 SuspendWithRawMonitorEnter.barrierLaunch.notify();
391             }
392 
393             SuspendWithRawMonitorEnter.logDebug("before enter threadLock");
394             retCode = rawMonitorEnter();
395             if (retCode != 0) {
396                 throw new RuntimeException("error in JVMTI RawMonitorEnter: " +
397                                            "retCode=" + retCode);
398             }
399             SuspendWithRawMonitorEnter.logDebug("enter threadLock");
400 
401             SuspendWithRawMonitorEnter.checkTestState(SuspendWithRawMonitorEnter.TS_CALL_RESUME);
402             SuspendWithRawMonitorEnter.testState = SuspendWithRawMonitorEnter.TS_CONTENDER_DONE;
403 
404             SuspendWithRawMonitorEnter.logDebug("before exit threadLock");
405             retCode = rawMonitorExit();
406             if (retCode != 0) {
407                 throw new RuntimeException("error in JVMTI RawMonitorExit: " +
408                                            "retCode=" + retCode);
409             }
410             SuspendWithRawMonitorEnter.logDebug("exit threadLock");
411         }
412         //
413         // Launch the resumer thread:
414         // - tries to grab the threadLock (should not block!)
415         // - grabs threadLock
416         // - resumes the contended thread
417         // - releases threadLock
418         //
419         else if (getName().equals("resumer")) {
420             synchronized(SuspendWithRawMonitorEnter.barrierResumer) {
421                 synchronized(SuspendWithRawMonitorEnter.barrierLaunch) {
422                     // tell main we are running
423                     SuspendWithRawMonitorEnter.testState = SuspendWithRawMonitorEnter.TS_RESUMER_RUNNING;
424                     SuspendWithRawMonitorEnter.barrierLaunch.notify();
425                 }
426                 SuspendWithRawMonitorEnter.logDebug("thread waiting");
427                 while (SuspendWithRawMonitorEnter.testState != SuspendWithRawMonitorEnter.TS_READY_TO_RESUME) {
428                     try {
429                         // wait for main to tell us when to continue
430                         SuspendWithRawMonitorEnter.barrierResumer.wait(0);
431                     } catch (InterruptedException ex) {
432                     }
433                 }
434             }
435             SuspendWithRawMonitorEnter.logDebug("before enter threadLock");
436             retCode = rawMonitorEnter();
437             if (retCode != 0) {
438                 throw new RuntimeException("error in JVMTI RawMonitorEnter: " +
439                                            "retCode=" + retCode);
440             }
441             SuspendWithRawMonitorEnter.logDebug("enter threadLock");
442 
443             SuspendWithRawMonitorEnter.checkTestState(SuspendWithRawMonitorEnter.TS_READY_TO_RESUME);
444             SuspendWithRawMonitorEnter.testState = SuspendWithRawMonitorEnter.TS_CALL_RESUME;
445 
446             // resume the contender thread so contender.join() can work
447             SuspendWithRawMonitorEnter.logDebug("before resume thread");
448             retCode = resumeThread(target);
449             if (retCode != 0) {
450                 throw new RuntimeException("error in JVMTI ResumeThread: " +
451                                            "retCode=" + retCode);
452             }
453             SuspendWithRawMonitorEnter.logDebug("resumed thread");
454 
455             SuspendWithRawMonitorEnter.logDebug("before exit threadLock");
456             retCode = rawMonitorExit();
457             if (retCode != 0) {
458                 throw new RuntimeException("error in JVMTI RawMonitorExit: " +
459                                            "retCode=" + retCode);
460             }
461             SuspendWithRawMonitorEnter.logDebug("exit threadLock");
462         }
463     }
464 }
465