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