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 6239400
  27  * @summary Tests NotificationBuffer doesn't hold locks when adding listeners,
  28  *  if test times out then deadlock is suspected.
  29  * @author Eamonn McManus
  30  *
  31  * @run clean NotificationBufferDeadlockTest
  32  * @run build NotificationBufferDeadlockTest
  33  * @run main NotificationBufferDeadlockTest
  34  */
  35 
  36 import java.lang.reflect.InvocationHandler;
  37 import java.lang.reflect.Method;
  38 import java.lang.reflect.Proxy;
  39 import java.net.MalformedURLException;
  40 import java.util.List;
  41 import java.util.Set;
  42 import java.util.Vector;
  43 import java.util.concurrent.CountDownLatch;
  44 import javax.management.*;
  45 import javax.management.remote.*;
  46 
  47 /*
  48  * Regression test for a rare but not unheard-of deadlock condition in
  49  * the notification buffer support for connector servers.
  50  * See bug 6239400 for the description of the bug and the example that
  51  * showed it up.
  52  *
  53  * Here we test that, when the connector server adds its listener to an
  54  * MBean, it is not holding a lock that would prevent another thread from
  55  * emitting a notification (from that MBean or another one).  This is
  56  * important, because we don't know how user MBeans might implement
  57  * NotificationBroadcaster.addNotificationListener, and in particular we
  58  * can't be sure that the method is well-behaved and can never do a
  59  * blocking operation, such as attempting to acquire a lock that is also
  60  * acquired when notifications are emitted.
  61  *
  62  * The test creates a special MBean whose addNotificationListener method
  63  * does the standard addNotificationListener logic inherited
  64  * from NotificationBroadcasterSupport, then
  65  * creates another thread that emits a notification from the same MBean.
  66  * The addNotificationListener method waits for this thread to complete.
  67  * If the notification buffer logic is incorrect, then emitting the
  68  * notification will attempt to acquire the lock on the buffer, but that
  69  * lock is being held by the thread that called addNotificationListener,
  70  * so there will be deadlock.
  71  *
  72  * We use this DeadlockMBean several times.  First, we create one and then
  73  * add a remote listener to it.  The first time you add a remote listener
  74  * through a connector server, the connector server adds its own listener
  75  * to all NotificationBroadcaster MBeans.  If it holds a lock while doing
  76  * this, we will see deadlock.
  77  *
  78  * Then we create a second DeadlockMBean.  When a new MBean is created that
  79  * is a NotificationBroadcaster, the connector server adds its listener to
  80  * that MBean too.  Again if it holds a lock while doing this, we will see
  81  * deadlock.
  82  *
  83  * Finally, we do some magic with MBeanServerForwarders so that while
  84  * queryNames is running (to find MBeans to which listeners must be added)
  85  * we will create new MBeans.  This tests that this tricky situation is
  86  * handled correctly.  It also tests the queryNames that is run when the
  87  * notification buffer is being destroyed (to remove the listeners).
  88  *
  89  * We cause all of our test MBeans to emit exactly one notification and
  90  * check that we have received exactly one notification from each MBean.
  91  * If the logic for adding the notification buffer's listener is incorrect
  92  * we could remove zero or two notifications from an MBean.
  93  */
  94 public class NotificationBufferDeadlockTest {
  95     public static void main(String[] args) throws Exception {
  96         System.out.println("Check no deadlock if notif sent while initial " +
  97                            "remote listeners being added");
  98         final String[] protos = {"rmi", "iiop", "jmxmp"};
  99         for (String p : protos) {
 100             try {
 101                 test(p);
 102             } catch (Exception e) {
 103                 System.out.println("TEST FAILED: GOT EXCEPTION:");
 104                 e.printStackTrace(System.out);
 105                 failure = e.toString();
 106             }
 107         }
 108         if (failure == null)
 109             return;
 110         else
 111             throw new Exception("TEST FAILED: " + failure);
 112     }
 113 
 114     private static void test(String proto) throws Exception {
 115         System.out.println("Testing protocol " + proto);
 116         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 117         ObjectName testName = newName();
 118         DeadlockTest test = new DeadlockTest();
 119         mbs.registerMBean(test, testName);
 120         JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + ":///");
 121         JMXConnectorServer cs;
 122         try {
 123             cs =
 124                 JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
 125         } catch (MalformedURLException e) {
 126             System.out.println("...protocol not supported, ignoring");
 127             return;
 128         }
 129 
 130         MBeanServerForwarder createDuringQueryForwarder = (MBeanServerForwarder)
 131             Proxy.newProxyInstance(new Object() {}.getClass().getClassLoader(),
 132                                    new Class[] {MBeanServerForwarder.class},
 133                                    new CreateDuringQueryInvocationHandler());
 134         cs.setMBeanServerForwarder(createDuringQueryForwarder);
 135         cs.start();
 136         JMXServiceURL addr = cs.getAddress();
 137         JMXConnector cc = JMXConnectorFactory.connect(addr);
 138         MBeanServerConnection mbsc = cc.getMBeanServerConnection();
 139         try {
 140             String fail = test(mbsc, testName);
 141             if (fail != null)
 142                 System.out.println("FAILED: " + fail);
 143             failure = fail;
 144         } finally {
 145             cc.close();
 146             cs.stop();
 147         }
 148     }
 149 
 150     private static String test(MBeanServerConnection mbsc,
 151                                ObjectName testName) throws Exception {
 152 
 153         NotificationListener dummyListener = new NotificationListener() {
 154             public void handleNotification(Notification n, Object h) {
 155             }
 156         };
 157         thisFailure = null;
 158         mbsc.addNotificationListener(testName, dummyListener, null, null);
 159         if (thisFailure != null)
 160             return thisFailure;
 161         ObjectName newName = newName();
 162         mbsc.createMBean(DeadlockTest.class.getName(), newName);
 163         if (thisFailure != null)
 164             return thisFailure;
 165         Set<ObjectName> names =
 166             mbsc.queryNames(new ObjectName("d:type=DeadlockTest,*"), null);
 167         System.out.printf("...found %d test MBeans\n", names.size());
 168 
 169         sources.clear();
 170         countListener = new MyListener(names.size());
 171 
 172         for (ObjectName name : names)
 173             mbsc.addNotificationListener(name, countListener, null, null);
 174         if (thisFailure != null)
 175             return thisFailure;
 176         for (ObjectName name : names)
 177             mbsc.invoke(name, "send", null, null);
 178 
 179         countListener.waiting();
 180 
 181         if (!sources.containsAll(names))
 182             return "missing names: " + sources;
 183         return thisFailure;
 184     }
 185 
 186     public static interface DeadlockTestMBean {
 187         public void send();
 188     }
 189 
 190     public static class DeadlockTest extends NotificationBroadcasterSupport
 191             implements DeadlockTestMBean {
 192         @Override
 193         public void addNotificationListener(NotificationListener listener,
 194                                             NotificationFilter filter,
 195                                             Object handback) {
 196             super.addNotificationListener(listener, filter, handback);
 197             Thread t = new Thread() {
 198                 @Override
 199                 public void run() {
 200                     Notification n =
 201                         new Notification("type", DeadlockTest.this, 0L);
 202                     DeadlockTest.this.sendNotification(n);
 203                 }
 204             };
 205             t.start();
 206             System.out.println("DeadlockTest-addNotificationListener waiting for the sending thread to die...");
 207             try {
 208                 t.join(); //if times out here then deadlock is suspected
 209                 System.out.println("DeadlockTest-addNotificationListener OK.");
 210             } catch (Exception e) {
 211                 thisFailure = "Join exception: " + e;
 212             }
 213         }
 214 
 215         public void send() {
 216             sendNotification(new Notification(TESTING_TYPE, DeadlockTest.this, 1L));
 217         }
 218     }
 219 
 220     private static class CreateDuringQueryInvocationHandler
 221             implements InvocationHandler {
 222         public Object invoke(Object proxy, Method m, Object[] args)
 223                 throws Throwable {
 224             if (m.getName().equals("setMBeanServer")) {
 225                 mbs = (MBeanServer) args[0];
 226                 return null;
 227             }
 228             createMBeanIfQuery(m);
 229             Object ret = m.invoke(mbs, args);
 230             createMBeanIfQuery(m);
 231             return ret;
 232         }
 233 
 234         private void createMBeanIfQuery(Method m) throws InterruptedException {
 235             if (m.getName().equals("queryNames")) {
 236                 Thread t = new Thread() {
 237                     public void run() {
 238                         try {
 239                             mbs.createMBean(DeadlockTest.class.getName(),
 240                                             newName());
 241                         } catch (Exception e) {
 242                             e.printStackTrace();
 243                             thisFailure = e.toString();
 244                         }
 245                     }
 246                 };
 247                 t.start();
 248                 System.out.println("CreateDuringQueryInvocationHandler-createMBeanIfQuery waiting for the creating thread to die...");
 249                 t.join();  // if times out here then deadlock is suspected
 250                 System.out.println("CreateDuringQueryInvocationHandler-createMBeanIfQuery OK");
 251             }
 252         }
 253 
 254         private MBeanServer mbs;
 255     }
 256 
 257     private static synchronized ObjectName newName() {
 258         try {
 259             return new ObjectName("d:type=DeadlockTest,instance=" +
 260                                   ++nextNameIndex);
 261         } catch (MalformedObjectNameException e) {
 262             throw new IllegalArgumentException("bad ObjectName", e);
 263         }
 264     }
 265 
 266     private static class MyListener implements NotificationListener {
 267         public MyListener(int waitNB) {
 268             count = new CountDownLatch(waitNB);
 269         }
 270 
 271         public void handleNotification(Notification n, Object h) {
 272             System.out.println("MyListener got: " + n.getSource() + " " + n.getType());
 273 
 274             if (TESTING_TYPE.equals(n.getType())) {
 275                 sources.add((ObjectName) n.getSource());
 276                 count.countDown();
 277             }
 278         }
 279 
 280         public void waiting() throws InterruptedException {
 281             System.out.println("MyListener-waiting ...");
 282             count.await(); // if times out here then deadlock is suspected
 283             System.out.println("MyListener-waiting done!");
 284         }
 285 
 286         private final CountDownLatch count;
 287     }
 288 
 289     static String thisFailure;
 290     static String failure;
 291     static int nextNameIndex;
 292 
 293     private static MyListener countListener;
 294     private static final List<ObjectName> sources = new Vector();
 295 
 296     private static final String TESTING_TYPE = "testing_type";
 297 }