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 }