BUG-4514: clean children in InternalJMXRegistrator
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / jmx / InternalJMXRegistrator.java
index 85ad8c51340c045cc051150ec9e6159fc7144fbb..71656c27bcc0559c91a42bb1049faad30da5077d 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.config.manager.impl.jmx;
 
-import java.io.Closeable;
+import com.google.common.base.Preconditions;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -25,62 +25,82 @@ import javax.management.QueryExp;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class InternalJMXRegistrator implements Closeable {
-    private static final Logger LOG = LoggerFactory
-            .getLogger(InternalJMXRegistrator.class);
-    private final MBeanServer configMBeanServer;
+abstract class InternalJMXRegistrator implements AutoCloseable {
+    private static final class Root extends InternalJMXRegistrator {
+        private final MBeanServer mbeanServer;
 
-    public InternalJMXRegistrator(MBeanServer configMBeanServer) {
-        this.configMBeanServer = configMBeanServer;
+        Root(final MBeanServer mbeanServer) {
+            this.mbeanServer = Preconditions.checkNotNull(mbeanServer);
+        }
+
+        @Override
+        MBeanServer getMBeanServer() {
+            return mbeanServer;
+        }
     }
 
-    static class InternalJMXRegistration implements AutoCloseable {
-        private final InternalJMXRegistrator internalJMXRegistrator;
-        private final ObjectName on;
+    private static final class Nested extends InternalJMXRegistrator {
+        private final InternalJMXRegistrator parent;
 
-        InternalJMXRegistration(InternalJMXRegistrator internalJMXRegistrator,
-                                ObjectName on) {
-            this.internalJMXRegistrator = internalJMXRegistrator;
-            this.on = on;
+        Nested(final InternalJMXRegistrator parent) {
+            this.parent = Preconditions.checkNotNull(parent);
+        }
+
+        @Override
+        MBeanServer getMBeanServer() {
+            return parent.getMBeanServer();
         }
 
         @Override
         public void close() {
-            internalJMXRegistrator.unregisterMBean(on);
+            try {
+                super.close();
+            } finally {
+                parent.removeChild(this);
+            }
         }
     }
 
+    private static final Logger LOG = LoggerFactory.getLogger(InternalJMXRegistrator.class);
     @GuardedBy("this")
-    private final Set<ObjectName> registeredObjectNames = new HashSet<>();
+    private final Set<ObjectName> registeredObjectNames = new HashSet<>(1);
     @GuardedBy("this")
-    private final List<InternalJMXRegistrator> children = new ArrayList<>();
+    private final List<Nested> children = new ArrayList<>();
+
+    public static InternalJMXRegistrator create(final MBeanServer configMBeanServer) {
+        return new Root(configMBeanServer);
+    }
 
-    public synchronized InternalJMXRegistration registerMBean(Object object,
-                                                              ObjectName on) throws InstanceAlreadyExistsException {
+    public final synchronized InternalJMXRegistration registerMBean(final Object object, final ObjectName on)
+            throws InstanceAlreadyExistsException {
         try {
-            configMBeanServer.registerMBean(object, on);
-        } catch (MBeanRegistrationException | NotCompliantMBeanException e) {
-            throw new IllegalStateException(e);
+            getMBeanServer().registerMBean(object, on);
+        } catch (NotCompliantMBeanException e) {
+            throw new IllegalArgumentException("Object does not comply to JMX", e);
+        } catch (MBeanRegistrationException e) {
+            throw new IllegalStateException("Failed to register " + on, e);
         }
+
         registeredObjectNames.add(on);
         return new InternalJMXRegistration(this, on);
     }
 
-    private synchronized void unregisterMBean(ObjectName on) {
+    final synchronized void unregisterMBean(final ObjectName on) {
         // first check that on was registered using this instance
         boolean removed = registeredObjectNames.remove(on);
-        if (!removed) {
-            throw new IllegalStateException("Cannot unregister - ObjectName not found in 'registeredObjectNames': " + on);
-        }
+        Preconditions.checkState(removed, "Cannot unregister - ObjectName not found in 'registeredObjectNames': {}", on);
+
         try {
-            configMBeanServer.unregisterMBean(on);
-        } catch (InstanceNotFoundException | MBeanRegistrationException e) {
-            throw new IllegalStateException(e);
+            getMBeanServer().unregisterMBean(on);
+        } catch (InstanceNotFoundException e) {
+            LOG.warn("MBean {} not found on server", on, e);
+        } catch (MBeanRegistrationException e) {
+            throw new IllegalStateException("Failed to unregister MBean " + on, e);
         }
     }
 
-    public synchronized InternalJMXRegistrator createChild() {
-        InternalJMXRegistrator child = new InternalJMXRegistrator(configMBeanServer);
+    public final synchronized InternalJMXRegistrator createChild() {
+        final Nested child = new Nested(this);
         children.add(child);
         return child;
     }
@@ -89,60 +109,70 @@ public class InternalJMXRegistrator implements Closeable {
      * Allow close to be called multiple times.
      */
     @Override
-    public synchronized void close() {
-        // close children
-        for (InternalJMXRegistrator child : children) {
-            child.close();
-        }
-        // close registered ONs
-        for (ObjectName on : registeredObjectNames) {
-            try {
-                configMBeanServer.unregisterMBean(on);
-            } catch (Exception e) {
-                LOG.warn("Ignoring error while unregistering {}", on, e);
-            }
-        }
-        registeredObjectNames.clear();
+    public void close() {
+        internalClose();
     }
 
-    public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
-        return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass);
+    public final <T> T newMBeanProxy(final ObjectName objectName, final Class<T> interfaceClass) {
+        return JMX.newMBeanProxy(getMBeanServer(), objectName, interfaceClass);
     }
 
-    public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass,
-                               boolean notificationBroadcaster) {
-        return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass,
-                notificationBroadcaster);
+    public final <T> T newMBeanProxy(final ObjectName objectName, final Class<T> interfaceClass,
+            final boolean notificationBroadcaster) {
+        return JMX.newMBeanProxy(getMBeanServer(), objectName, interfaceClass, notificationBroadcaster);
     }
 
-    public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
-        return JMX
-                .newMXBeanProxy(configMBeanServer, objectName, interfaceClass);
+    public final <T> T newMXBeanProxy(final ObjectName objectName, final Class<T> interfaceClass) {
+        return JMX.newMXBeanProxy(getMBeanServer(), objectName, interfaceClass);
     }
 
-    public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass,
-                                boolean notificationBroadcaster) {
-        return JMX.newMXBeanProxy(configMBeanServer, objectName,
-                interfaceClass, notificationBroadcaster);
+    public final <T> T newMXBeanProxy(final ObjectName objectName, final Class<T> interfaceClass,
+            final boolean notificationBroadcaster) {
+        return JMX.newMXBeanProxy(getMBeanServer(), objectName, interfaceClass, notificationBroadcaster);
     }
 
-    public Set<ObjectName> getRegisteredObjectNames() {
+    public final Set<ObjectName> getRegisteredObjectNames() {
         return Collections.unmodifiableSet(registeredObjectNames);
     }
 
-    public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
-        Set<ObjectName> result = configMBeanServer.queryNames(name, query);
+    public final Set<ObjectName> queryNames(final ObjectName name, final QueryExp query) {
         // keep only those that were registered using this instance
-        return getSameNames(result);
+        return getSameNames(getMBeanServer().queryNames(name, query));
+    }
+
+    abstract MBeanServer getMBeanServer();
+
+    synchronized void removeChild(final InternalJMXRegistrator child) {
+        children.remove(child);
     }
 
-    private synchronized Set<ObjectName> getSameNames(Set<ObjectName> superSet) {
-        Set<ObjectName> result = new HashSet<>(superSet);
+    private synchronized void internalClose() {
+        // close all children
+        for (InternalJMXRegistrator child : children) {
+            // This bypasses the call to removeChild(), preventing a potential deadlock when children are being closed
+            // concurrently
+            child.internalClose();
+        }
+        children.clear();
+
+        // close registered ONs
+        for (ObjectName on : registeredObjectNames) {
+            try {
+                getMBeanServer().unregisterMBean(on);
+            } catch (Exception e) {
+                LOG.warn("Ignoring error while unregistering {}", on, e);
+            }
+        }
+        registeredObjectNames.clear();
+    }
+
+    private synchronized Set<ObjectName> getSameNames(final Set<ObjectName> superSet) {
+        final Set<ObjectName> result = new HashSet<>(superSet);
         result.retainAll(registeredObjectNames);
+
         for (InternalJMXRegistrator child : children) {
             result.addAll(child.getSameNames(superSet));
         }
         return result;
     }
-
 }