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