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  *
  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 
  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 {
  73         TestCase(String description, When when) {
  74             this.description = description;
  75             this.when = when;
  76         }
  77 
  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 
 190         abstract void doSensitiveThing(GaugeMonitorMBean monitorProxy,
 191                                        ObjectName observedName);
 192 
 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 {
 237         public int getThing();
 238         public void setThing(int thing);
 239         public int getGetCount();
 240     }
 241 
 242     public static class Test implements TestMBean {
 243         public Test(Runnable runWithinGetAttribute) {
 244             this.runWithinGetAttribute = runWithinGetAttribute;
 245         }
 246 
 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 
 259         public void setThing(int thing) {
 260             this.thing = thing;
 261         }
 262 
 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 }