1 /*
   2  * Copyright (c) 2015, 2018, 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.wait";
  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 }