1 /*
  2  * Copyright (c) 2015, 2022, 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  Call Object.wait() method. Check that monitor information
 27  *           presented in the stack is correct. Call notifyAll method
 28  *           monitor info have to disappear from the stack.
 29  *           Repeats the same scenario calling interrupt() method
 30  * @modules java.base/jdk.internal.misc
 31  * @library /test/lib
 32  * @library ../share
 33  * @run main/othervm -XX:+UsePerfData WaitNotifyThreadTest
 34  */
 35 import common.ToolResults;
 36 import java.util.Iterator;
 37 import utils.*;
 38 
 39 public class WaitNotifyThreadTest {
 40 
 41     private Object monitor = new Object();;
 42     private final String OBJECT = "a java.lang.Object";
 43     private final String OBJECT_WAIT = "java.lang.Object.wait0";
 44     private final String RUN_METHOD = "WaitNotifyThreadTest$WaitThread.run";
 45 
 46     interface Action {
 47         void doAction(Thread thread);
 48     }
 49 
 50     class ActionNotify implements Action {
 51 
 52         @Override
 53         public void doAction(Thread thread) {
 54             // Notify the waiting thread, so it stops waiting and sleeps
 55             synchronized (monitor) {
 56                 monitor.notifyAll();
 57             }
 58             // Wait until MyWaitingThread exits the monitor and sleeps
 59             while (thread.getState() != Thread.State.TIMED_WAITING) {}
 60         }
 61     }
 62 
 63     class ActionInterrupt implements Action {
 64 
 65         @Override
 66         public void doAction(Thread thread) {
 67             // Interrupt the thread
 68             thread.interrupt();
 69             // Wait until MyWaitingThread exits the monitor and sleeps
 70             while (thread.getState() != Thread.State.TIMED_WAITING) {}
 71         }
 72     }
 73 
 74     class WaitThread extends Thread {
 75 
 76         @Override
 77         public void run() {
 78             try {
 79                 synchronized (monitor) {
 80                     monitor.wait();
 81                 }
 82             } catch (InterruptedException x) {
 83 
 84             }
 85             Utils.sleep();
 86         }
 87     }
 88 
 89     public static void main(String[] args) throws Exception {
 90         new WaitNotifyThreadTest().doTest();
 91     }
 92 
 93     private void doTest() throws Exception {
 94 
 95         // Verify stack trace consistency when notifying the thread
 96         doTest(new ActionNotify());
 97 
 98         // Verify stack trace consistency when interrupting the thread
 99         doTest(new ActionInterrupt());
100     }
101 
102     private void doTest(Action action) throws Exception {
103 
104         final String WAITING_THREAD_NAME = "MyWaitingThread";
105 
106         // Start a thread that just waits
107         WaitThread waitThread = new WaitThread();
108         waitThread.setName(WAITING_THREAD_NAME);
109         waitThread.start();
110         // Wait until MyWaitingThread enters the monitor
111         while (waitThread.getState() != Thread.State.WAITING) {}
112 
113         // Collect output from the jstack tool
114         JstackTool jstackTool = new JstackTool(ProcessHandle.current().pid());
115         ToolResults results = jstackTool.measure();
116 
117         // Analyze the jstack output for the patterns needed
118         JStack jstack1 = new DefaultFormat().parse(results.getStdoutString());
119         ThreadStack ti1 = jstack1.getThreadStack(WAITING_THREAD_NAME);
120         analyzeThreadStackWaiting(ti1);
121 
122         action.doAction(waitThread);
123 
124         // Collect output from the jstack tool again
125         results = jstackTool.measure();
126 
127         // Analyze the output again
128         JStack jstack2 = new DefaultFormat().parse(results.getStdoutString());
129         ThreadStack ti2 = jstack2.getThreadStack(WAITING_THREAD_NAME);
130         analyzeThreadStackNoWaiting(ti2);
131     }
132 
133     private void analyzeThreadStackWaiting(ThreadStack ti1) {
134         Iterator<MethodInfo> it = ti1.getStack().iterator();
135 
136         String monitorAddress = null;
137         while (it.hasNext()) {
138             MethodInfo mi = it.next();
139             if (mi.getName().startsWith(OBJECT_WAIT) && mi.getCompilationUnit() == null /*native method*/) {
140                 if (mi.getLocks().size() == 1) {
141                     MonitorInfo monInfo = mi.getLocks().getFirst();
142                     monitorAddress = monInfo.getMonitorAddress();
143                     assertMonitorInfo("waiting on", monInfo, monitorAddress, OBJECT_WAIT);
144                 } else {
145                     throw new RuntimeException(OBJECT_WAIT + " method has to contain one lock record but it contains "
146                                                + mi.getLocks().size());
147                 }
148             }
149 
150             if (mi.getName().startsWith(RUN_METHOD)) {
151                 if (monitorAddress == null) {
152                     throw new RuntimeException("Cannot found monitor info associated with " + OBJECT_WAIT + " method");
153                 }
154                 if (mi.getLocks().size() == 1) {
155                     MonitorInfo monInfo = mi.getLocks().getLast();
156                     if (monitorAddress.equals("no object reference available")) {
157                         monitorAddress = monInfo.getMonitorAddress();
158                     }
159                     assertMonitorInfo("locked", monInfo, monitorAddress, RUN_METHOD);
160                 }
161                 else {
162                     throw new RuntimeException(RUN_METHOD + " method has to contain one lock record but it contains "
163                                                + mi.getLocks().size());
164                 }
165             }
166         }
167     }
168 
169     private void assertMonitorInfo(String expectedMessage, MonitorInfo monInfo, String monitorAddress, String method) {
170         if (monInfo.getType().equals(expectedMessage)
171                 && compareMonitorClass(monInfo)
172                 && monInfo.getMonitorAddress().equals(
173                         monitorAddress)) {
174             System.out.println("Correct monitor info found in " + method + " method");
175         } else {
176             System.err.println("Error: incorrect monitor info: " + monInfo.getType() + ", " + monInfo.getMonitorClass() + ", " + monInfo.getMonitorAddress());
177             System.err.println("Expected: " + expectedMessage + ", a java.lang.Object, " + monitorAddress);
178             throw new RuntimeException("Incorrect lock record in " + method + " method");
179         }
180     }
181 
182     private boolean compareMonitorClass(MonitorInfo monInfo) {
183         // If monitor class info is present in the jstack output
184         // then compare it with the class of the actual monitor object
185         // If there is no monitor class info available then return true
186         return OBJECT.equals(monInfo.getMonitorClass()) || (monInfo.getMonitorClass() == null);
187     }
188 
189     private void analyzeThreadStackNoWaiting(ThreadStack ti2) {
190         Iterator<MethodInfo> it = ti2.getStack().iterator();
191 
192         while (it.hasNext()) {
193             MethodInfo mi = it.next();
194             if (mi.getLocks().size() != 0) {
195                 throw new RuntimeException("Unexpected lock record in "
196                         + mi.getName() + " method");
197             }
198         }
199     }
200 
201 }