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 }