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