1 /*
2  * Copyright (c) 2005, 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  * @bug 6303187
27  * @summary Test that no locks are held when a monitor attribute is sampled
28  * or notif delivered.
29  * @author Eamonn McManus
30  *
31  * @library /test/lib
32  *
33  * @run clean GaugeMonitorDeadlockTest
34  * @run build GaugeMonitorDeadlockTest
35  * @run main GaugeMonitorDeadlockTest 1
36  * @run main GaugeMonitorDeadlockTest 2
37  * @run main GaugeMonitorDeadlockTest 3
38  * @run main GaugeMonitorDeadlockTest 4
39  */
40 
41 import java.lang.management.ManagementFactory;
42 import java.lang.management.ThreadInfo;
43 import java.lang.management.ThreadMXBean;
44 import java.util.concurrent.atomic.AtomicInteger;
45 import javax.management.JMX;
46 import javax.management.MBeanServer;
47 import javax.management.Notification;
48 import javax.management.NotificationListener;
49 import javax.management.ObjectName;
50 import javax.management.monitor.GaugeMonitor;
51 import javax.management.monitor.GaugeMonitorMBean;
52 
53 import jdk.test.lib.Utils;
54 
55 public class GaugeMonitorDeadlockTest {
56     private static enum When {IN_GET_ATTRIBUTE, IN_NOTIFY};
57     private static long checkingTime;
58 
main(String[] args)59     public static void main(String[] args) throws Exception {
60         if (args.length != 1)
61             throw new Exception("Arg should be test number");
62         checkingTime = Utils.adjustTimeout(1000); // default 1s timeout
63         System.out.println("=== checkingTime = " + checkingTime + "ms");
64 
65         int testNo = Integer.parseInt(args[0]) - 1;
66         TestCase test = testCases[testNo];
67         System.out.println("Test: " + test.getDescription());
68         test.run();
69         System.out.println("Test passed");
70     }
71 
72     private static abstract class TestCase {
TestCase(String description, When when)73         TestCase(String description, When when) {
74             this.description = description;
75             this.when = when;
76         }
77 
run()78         void run() throws Exception {
79             final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
80             final ObjectName observedName = new ObjectName("a:b=c");
81             final ObjectName monitorName = new ObjectName("a:type=Monitor");
82             mbs.registerMBean(new GaugeMonitor(), monitorName);
83             final GaugeMonitorMBean monitorProxy =
84                 JMX.newMBeanProxy(mbs, monitorName, GaugeMonitorMBean.class);
85             final TestMBean observedProxy =
86                 JMX.newMBeanProxy(mbs, observedName, TestMBean.class);
87 
88             final Runnable sensitiveThing = new Runnable() {
89                 public void run() {
90                     doSensitiveThing(monitorProxy, observedName);
91                 }
92             };
93 
94             final Runnable nothing = new Runnable() {
95                 public void run() {}
96             };
97 
98             final Runnable withinGetAttribute =
99                 (when == When.IN_GET_ATTRIBUTE) ? sensitiveThing : nothing;
100 
101             mbs.registerMBean(new Test(withinGetAttribute), observedName);
102             monitorProxy.addObservedObject(observedName);
103             monitorProxy.setObservedAttribute("Thing");
104             monitorProxy.setThresholds(105, 100);
105             monitorProxy.setGranularityPeriod(10L); // 10 ms
106             monitorProxy.setNotifyHigh(true);
107             monitorProxy.setNotifyLow(true);
108 
109             System.out.println("=== Waiting observedProxy.getGetCount() to be "
110                     + "changed, presumable deadlock if timeout?");
111             final int initGetCount = observedProxy.getGetCount();
112             monitorProxy.start();
113 
114             long checkedTime = System.currentTimeMillis();
115             long nowTime;
116             ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
117             while (observedProxy.getGetCount() == initGetCount) {
118                 Thread.sleep(100);
119 
120                 nowTime = System.currentTimeMillis();
121                 if (nowTime - checkedTime >= checkingTime) {
122                     System.out.println("=== Checking deadlocked ...");
123                     if (threadMXBean.findDeadlockedThreads() != null) {
124                         for (ThreadInfo info : threadMXBean.dumpAllThreads(true, true)) {
125                             System.out.println(info);
126                         }
127                         throw new Error("Found deadlocked threads: "
128                                 + threadMXBean.findDeadlockedThreads().length);
129                     }
130                     checkedTime = System.currentTimeMillis();
131                 }
132             }
133 
134             // This won't show up as a deadlock in CTRL-\ or in
135             // ThreadMXBean.findDeadlockedThreads(), because they don't
136             // see that thread A is waiting for thread B (B.join()), and
137             // thread B is waiting for a lock held by thread A
138 
139             // Now we know the monitor has observed the initial value,
140             // so if we want to test notify behaviour we can trigger by
141             // exceeding the threshold.
142             if (when == When.IN_NOTIFY) {
143                 final Thread testedThread = new Thread(sensitiveThing);
144                 final AtomicInteger notifCount = new AtomicInteger();
145                 final NotificationListener listener = new NotificationListener() {
146                     public void handleNotification(Notification n, Object h) {
147                         testedThread.start();
148                         try {
149                             testedThread.join();
150                         } catch (InterruptedException e) {
151                             throw new RuntimeException(e);
152                         }
153                         notifCount.incrementAndGet();
154                     }
155                 };
156                 mbs.addNotificationListener(monitorName, listener, null, null);
157                 observedProxy.setThing(1000);
158                 System.out.println("=== Waiting notifications, presumable "
159                         + "deadlock if timeout?");
160                 long startTime = System.currentTimeMillis();
161                 checkedTime = startTime;
162                 while (notifCount.get() == 0) {
163                     Thread.sleep(100);
164 
165                     nowTime = System.currentTimeMillis();
166                     if (nowTime - checkedTime >= checkingTime) {
167                         System.out.println("=== Checking the thread state ...");
168                         if (testedThread.isAlive()) {
169                             System.out.println("=== Waiting testedThread to die "
170                                     + "after " + (nowTime - startTime) + "ms");
171 
172                             ThreadInfo tinfo = threadMXBean.getThreadInfo(testedThread.getId());
173                             if (Thread.State.BLOCKED.equals(tinfo.getThreadState())) {
174                                 for (ThreadInfo info : threadMXBean.dumpAllThreads(true, true)) {
175                                     System.out.println(info);
176                                 }
177                             } else {
178                                 System.out.println(tinfo);
179                             }
180                         } else {
181                             System.out.println("=== The testedThread is dead as wished, "
182                                     + "the test must be passed soon.");
183                         }
184                         checkedTime = System.currentTimeMillis();
185                     }
186                 }
187             }
188         }
189 
doSensitiveThing(GaugeMonitorMBean monitorProxy, ObjectName observedName)190         abstract void doSensitiveThing(GaugeMonitorMBean monitorProxy,
191                                        ObjectName observedName);
192 
getDescription()193         String getDescription() {
194             return description;
195         }
196 
197         private final String description;
198         private final When when;
199     }
200 
201     private static final TestCase[] testCases = {
202         new TestCase("Remove monitored MBean within monitored getAttribute",
203                      When.IN_GET_ATTRIBUTE) {
204             @Override
205             void doSensitiveThing(GaugeMonitorMBean monitorProxy,
206                                   ObjectName observedName) {
207                 monitorProxy.removeObservedObject(observedName);
208             }
209         },
210         new TestCase("Stop monitor within monitored getAttribute",
211                      When.IN_GET_ATTRIBUTE) {
212             @Override
213             void doSensitiveThing(GaugeMonitorMBean monitorProxy,
214                                   ObjectName observedName) {
215                 monitorProxy.stop();
216             }
217         },
218         new TestCase("Remove monitored MBean within threshold listener",
219                      When.IN_NOTIFY) {
220             @Override
221             void doSensitiveThing(GaugeMonitorMBean monitorProxy,
222                                   ObjectName observedName) {
223                 monitorProxy.removeObservedObject(observedName);
224             }
225         },
226         new TestCase("Stop monitor within threshold listener",
227                      When.IN_NOTIFY) {
228             @Override
229             void doSensitiveThing(GaugeMonitorMBean monitorProxy,
230                                   ObjectName observedName) {
231                 monitorProxy.stop();
232             }
233         },
234     };
235 
236     public static interface TestMBean {
getThing()237         public int getThing();
setThing(int thing)238         public void setThing(int thing);
getGetCount()239         public int getGetCount();
240     }
241 
242     public static class Test implements TestMBean {
Test(Runnable runWithinGetAttribute)243         public Test(Runnable runWithinGetAttribute) {
244             this.runWithinGetAttribute = runWithinGetAttribute;
245         }
246 
getThing()247         public int getThing() {
248             Thread t = new Thread(runWithinGetAttribute);
249             t.start();
250             try {
251                 t.join();
252             } catch (InterruptedException e) {
253                 throw new RuntimeException(e);
254             }
255             getCount++;
256             return thing;
257         }
258 
setThing(int thing)259         public void setThing(int thing) {
260             this.thing = thing;
261         }
262 
getGetCount()263         public int getGetCount() {
264             return getCount;
265         }
266 
267         private final Runnable runWithinGetAttribute;
268         private volatile int getCount;
269         private volatile int thing;
270     }
271 }
272