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 {
main(String[] args)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 
test(String proto)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 
test(MBeanServerConnection mbsc, ObjectName testName)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 {
send()186         public void send();
187     }
188 
189     public static class DeadlockTest extends NotificationBroadcasterSupport
190             implements DeadlockTestMBean {
191         @Override
addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)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 
send()214         public void send() {
215             sendNotification(new Notification(TESTING_TYPE, DeadlockTest.this, 1L));
216         }
217     }
218 
219     private static class CreateDuringQueryInvocationHandler
220             implements InvocationHandler {
invoke(Object proxy, Method m, Object[] args)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 
createMBeanIfQuery(Method m)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 
newName()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 {
MyListener(int waitNB)266         public MyListener(int waitNB) {
267             count = new CountDownLatch(waitNB);
268         }
269 
handleNotification(Notification n, Object h)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 
waiting()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 }
297