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