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