3789983e89
Reviewed-by: darcy, ihse
298 lines
12 KiB
Java
298 lines
12 KiB
Java
/*
|
|
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 6239400
|
|
* @summary Tests NotificationBuffer doesn't hold locks when adding listeners,
|
|
* if test times out then deadlock is suspected.
|
|
* @author Eamonn McManus
|
|
*
|
|
* @run clean NotificationBufferDeadlockTest
|
|
* @run build NotificationBufferDeadlockTest
|
|
* @run main NotificationBufferDeadlockTest
|
|
*/
|
|
|
|
import java.lang.reflect.InvocationHandler;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Proxy;
|
|
import java.net.MalformedURLException;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.Vector;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import javax.management.*;
|
|
import javax.management.remote.*;
|
|
|
|
/*
|
|
* Regression test for a rare but not unheard-of deadlock condition in
|
|
* the notification buffer support for connector servers.
|
|
* See bug 6239400 for the description of the bug and the example that
|
|
* showed it up.
|
|
*
|
|
* Here we test that, when the connector server adds its listener to an
|
|
* MBean, it is not holding a lock that would prevent another thread from
|
|
* emitting a notification (from that MBean or another one). This is
|
|
* important, because we don't know how user MBeans might implement
|
|
* NotificationBroadcaster.addNotificationListener, and in particular we
|
|
* can't be sure that the method is well-behaved and can never do a
|
|
* blocking operation, such as attempting to acquire a lock that is also
|
|
* acquired when notifications are emitted.
|
|
*
|
|
* The test creates a special MBean whose addNotificationListener method
|
|
* does the standard addNotificationListener logic inherited
|
|
* from NotificationBroadcasterSupport, then
|
|
* creates another thread that emits a notification from the same MBean.
|
|
* The addNotificationListener method waits for this thread to complete.
|
|
* If the notification buffer logic is incorrect, then emitting the
|
|
* notification will attempt to acquire the lock on the buffer, but that
|
|
* lock is being held by the thread that called addNotificationListener,
|
|
* so there will be deadlock.
|
|
*
|
|
* We use this DeadlockMBean several times. First, we create one and then
|
|
* add a remote listener to it. The first time you add a remote listener
|
|
* through a connector server, the connector server adds its own listener
|
|
* to all NotificationBroadcaster MBeans. If it holds a lock while doing
|
|
* this, we will see deadlock.
|
|
*
|
|
* Then we create a second DeadlockMBean. When a new MBean is created that
|
|
* is a NotificationBroadcaster, the connector server adds its listener to
|
|
* that MBean too. Again if it holds a lock while doing this, we will see
|
|
* deadlock.
|
|
*
|
|
* Finally, we do some magic with MBeanServerForwarders so that while
|
|
* queryNames is running (to find MBeans to which listeners must be added)
|
|
* we will create new MBeans. This tests that this tricky situation is
|
|
* handled correctly. It also tests the queryNames that is run when the
|
|
* notification buffer is being destroyed (to remove the listeners).
|
|
*
|
|
* We cause all of our test MBeans to emit exactly one notification and
|
|
* check that we have received exactly one notification from each MBean.
|
|
* If the logic for adding the notification buffer's listener is incorrect
|
|
* we could remove zero or two notifications from an MBean.
|
|
*/
|
|
public class NotificationBufferDeadlockTest {
|
|
public static void main(String[] args) throws Exception {
|
|
System.out.println("Check no deadlock if notif sent while initial " +
|
|
"remote listeners being added");
|
|
final String[] protos = {"rmi", "iiop", "jmxmp"};
|
|
for (String p : protos) {
|
|
try {
|
|
test(p);
|
|
} catch (Exception e) {
|
|
System.out.println("TEST FAILED: GOT EXCEPTION:");
|
|
e.printStackTrace(System.out);
|
|
failure = e.toString();
|
|
}
|
|
}
|
|
if (failure == null)
|
|
return;
|
|
else
|
|
throw new Exception("TEST FAILED: " + failure);
|
|
}
|
|
|
|
private static void test(String proto) throws Exception {
|
|
System.out.println("Testing protocol " + proto);
|
|
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
|
|
ObjectName testName = newName();
|
|
DeadlockTest test = new DeadlockTest();
|
|
mbs.registerMBean(test, testName);
|
|
JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + ":///");
|
|
JMXConnectorServer cs;
|
|
try {
|
|
cs =
|
|
JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
|
|
} catch (MalformedURLException e) {
|
|
System.out.println("...protocol not supported, ignoring");
|
|
return;
|
|
}
|
|
|
|
MBeanServerForwarder createDuringQueryForwarder = (MBeanServerForwarder)
|
|
Proxy.newProxyInstance(new Object() {}.getClass().getClassLoader(),
|
|
new Class[] {MBeanServerForwarder.class},
|
|
new CreateDuringQueryInvocationHandler());
|
|
cs.setMBeanServerForwarder(createDuringQueryForwarder);
|
|
cs.start();
|
|
JMXServiceURL addr = cs.getAddress();
|
|
JMXConnector cc = JMXConnectorFactory.connect(addr);
|
|
MBeanServerConnection mbsc = cc.getMBeanServerConnection();
|
|
try {
|
|
String fail = test(mbsc, testName);
|
|
if (fail != null)
|
|
System.out.println("FAILED: " + fail);
|
|
failure = fail;
|
|
} finally {
|
|
cc.close();
|
|
cs.stop();
|
|
}
|
|
}
|
|
|
|
private static String test(MBeanServerConnection mbsc,
|
|
ObjectName testName) throws Exception {
|
|
|
|
NotificationListener dummyListener = new NotificationListener() {
|
|
public void handleNotification(Notification n, Object h) {
|
|
}
|
|
};
|
|
thisFailure = null;
|
|
mbsc.addNotificationListener(testName, dummyListener, null, null);
|
|
if (thisFailure != null)
|
|
return thisFailure;
|
|
ObjectName newName = newName();
|
|
mbsc.createMBean(DeadlockTest.class.getName(), newName);
|
|
if (thisFailure != null)
|
|
return thisFailure;
|
|
Set<ObjectName> names =
|
|
mbsc.queryNames(new ObjectName("d:type=DeadlockTest,*"), null);
|
|
System.out.printf("...found %d test MBeans\n", names.size());
|
|
|
|
sources.clear();
|
|
countListener = new MyListener(names.size());
|
|
|
|
for (ObjectName name : names)
|
|
mbsc.addNotificationListener(name, countListener, null, null);
|
|
if (thisFailure != null)
|
|
return thisFailure;
|
|
for (ObjectName name : names)
|
|
mbsc.invoke(name, "send", null, null);
|
|
|
|
countListener.waiting();
|
|
|
|
if (!sources.containsAll(names))
|
|
return "missing names: " + sources;
|
|
return thisFailure;
|
|
}
|
|
|
|
public static interface DeadlockTestMBean {
|
|
public void send();
|
|
}
|
|
|
|
public static class DeadlockTest extends NotificationBroadcasterSupport
|
|
implements DeadlockTestMBean {
|
|
@Override
|
|
public void addNotificationListener(NotificationListener listener,
|
|
NotificationFilter filter,
|
|
Object handback) {
|
|
super.addNotificationListener(listener, filter, handback);
|
|
Thread t = new Thread() {
|
|
@Override
|
|
public void run() {
|
|
Notification n =
|
|
new Notification("type", DeadlockTest.this, 0L);
|
|
DeadlockTest.this.sendNotification(n);
|
|
}
|
|
};
|
|
t.start();
|
|
System.out.println("DeadlockTest-addNotificationListener waiting for the sending thread to die...");
|
|
try {
|
|
t.join(); //if times out here then deadlock is suspected
|
|
System.out.println("DeadlockTest-addNotificationListener OK.");
|
|
} catch (Exception e) {
|
|
thisFailure = "Join exception: " + e;
|
|
}
|
|
}
|
|
|
|
public void send() {
|
|
sendNotification(new Notification(TESTING_TYPE, DeadlockTest.this, 1L));
|
|
}
|
|
}
|
|
|
|
private static class CreateDuringQueryInvocationHandler
|
|
implements InvocationHandler {
|
|
public Object invoke(Object proxy, Method m, Object[] args)
|
|
throws Throwable {
|
|
if (m.getName().equals("setMBeanServer")) {
|
|
mbs = (MBeanServer) args[0];
|
|
return null;
|
|
}
|
|
createMBeanIfQuery(m);
|
|
Object ret = m.invoke(mbs, args);
|
|
createMBeanIfQuery(m);
|
|
return ret;
|
|
}
|
|
|
|
private void createMBeanIfQuery(Method m) throws InterruptedException {
|
|
if (m.getName().equals("queryNames")) {
|
|
Thread t = new Thread() {
|
|
public void run() {
|
|
try {
|
|
mbs.createMBean(DeadlockTest.class.getName(),
|
|
newName());
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
thisFailure = e.toString();
|
|
}
|
|
}
|
|
};
|
|
t.start();
|
|
System.out.println("CreateDuringQueryInvocationHandler-createMBeanIfQuery waiting for the creating thread to die...");
|
|
t.join(); // if times out here then deadlock is suspected
|
|
System.out.println("CreateDuringQueryInvocationHandler-createMBeanIfQuery OK");
|
|
}
|
|
}
|
|
|
|
private MBeanServer mbs;
|
|
}
|
|
|
|
private static synchronized ObjectName newName() {
|
|
try {
|
|
return new ObjectName("d:type=DeadlockTest,instance=" +
|
|
++nextNameIndex);
|
|
} catch (MalformedObjectNameException e) {
|
|
throw new IllegalArgumentException("bad ObjectName", e);
|
|
}
|
|
}
|
|
|
|
private static class MyListener implements NotificationListener {
|
|
public MyListener(int waitNB) {
|
|
count = new CountDownLatch(waitNB);
|
|
}
|
|
|
|
public void handleNotification(Notification n, Object h) {
|
|
System.out.println("MyListener got: " + n.getSource() + " " + n.getType());
|
|
|
|
if (TESTING_TYPE.equals(n.getType())) {
|
|
sources.add((ObjectName) n.getSource());
|
|
count.countDown();
|
|
}
|
|
}
|
|
|
|
public void waiting() throws InterruptedException {
|
|
System.out.println("MyListener-waiting ...");
|
|
count.await(); // if times out here then deadlock is suspected
|
|
System.out.println("MyListener-waiting done!");
|
|
}
|
|
|
|
private final CountDownLatch count;
|
|
}
|
|
|
|
static String thisFailure;
|
|
static String failure;
|
|
static int nextNameIndex;
|
|
|
|
private static MyListener countListener;
|
|
private static final List<ObjectName> sources = new Vector();
|
|
|
|
private static final String TESTING_TYPE = "testing_type";
|
|
}
|