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