BUG-8327: GlobalBundleScanningSchemaServiceImpl should be a proxy 64/56264/5
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 28 Apr 2017 14:56:48 +0000 (16:56 +0200)
committerTom Pantelis <tompantelis@gmail.com>
Sun, 30 Apr 2017 20:19:06 +0000 (20:19 +0000)
We are currently running to separate services which assemble
the GlobalSchemaContext, which hurts our startup performance and
leads to wasted memory. This is an artefact of the mdsal split,
hence we should be getting the service from the MD-SAL and
just proxy to old interfaces.

This lowers the startup time for

feature:install odl-restconf odl-bgpcep-bgp
    odl-bgpcep-data-change-counter odl-netconf-topology

from 86s down to 67s (22%). Final retained heap size is also
lowered from 217MiB to 181MiB (16%)

Change-Id: I549e9512538bd83d86cfd2164d03e34bc9130c1e
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
opendaylight/md-sal/sal-schema-service/pom.xml
opendaylight/md-sal/sal-schema-service/src/main/java/org/opendaylight/controller/sal/schema/service/impl/GlobalBundleScanningSchemaServiceImpl.java
opendaylight/md-sal/sal-schema-service/src/main/java/org/opendaylight/controller/sal/schema/service/impl/SchemaServiceActivator.java [deleted file]
opendaylight/md-sal/sal-schema-service/src/main/resources/org/opendaylight/blueprint/sal-schema-service.xml [new file with mode: 0644]

index d86351f1c4384042a7bfccb653bc1c3dfc658b3d..4c01859eb1f6366fdec56e9811d1b49564fca0e3 100644 (file)
     </dependency>
   </dependencies>
 
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
-            <Bundle-Activator>org.opendaylight.controller.sal.schema.service.impl.SchemaServiceActivator</Bundle-Activator>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-
   <scm>
     <connection>scm:git:http://git.opendaylight.org/gerrit/controller.git</connection>
     <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
index 32a6291e5fd2f4162c186f5b1812359af4fb860d..4a4ae3b82acf9fbe5fbcf21b207f8e2ba6435c62 100644 (file)
  */
 package org.opendaylight.controller.sal.schema.service.impl;
 
-import static com.google.common.base.Preconditions.checkState;
-import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.CheckedFuture;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
+import com.google.common.util.concurrent.Futures;
+import java.util.HashSet;
+import java.util.Set;
 import javax.annotation.concurrent.GuardedBy;
 import org.opendaylight.controller.sal.core.api.model.SchemaService;
 import org.opendaylight.controller.sal.core.api.model.YangTextSourceProvider;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.concepts.Registration;
-import org.opendaylight.yangtools.util.ListenerRegistry;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
+import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.BundleTracker;
-import org.osgi.util.tracker.BundleTrackerCustomizer;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService,
-        ServiceTrackerCustomizer<SchemaContextListener, SchemaContextListener>, YangTextSourceProvider, AutoCloseable {
-    private static final Logger LOG = LoggerFactory.getLogger(GlobalBundleScanningSchemaServiceImpl.class);
-    private static final long FRAMEWORK_BUNDLE_ID = 0;
+public final class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService,
+        YangTextSourceProvider, AutoCloseable {
 
-    @GuardedBy(value = "lock")
-    private final ListenerRegistry<SchemaContextListener> listeners = new ListenerRegistry<>();
-    private final YangTextSchemaContextResolver contextResolver = YangTextSchemaContextResolver.create("global-bundle");
-    private final BundleScanner scanner = new BundleScanner();
+    @GuardedBy("lock")
+    private final Set<ListenerRegistration<?>> listeners = new HashSet<>();
     private final Object lock = new Object();
-    private final BundleContext context;
-
-    private ServiceTracker<SchemaContextListener, SchemaContextListener> listenerTracker;
-    private BundleTracker<Iterable<Registration>> bundleTracker;
-    private boolean starting = true;
-
-    private volatile boolean stopping;
-
-    private GlobalBundleScanningSchemaServiceImpl(final BundleContext context) {
-        this.context = Preconditions.checkNotNull(context);
-    }
+    private final DOMSchemaService schemaService;
+    private final DOMYangTextSourceProvider yangProvider;
 
-    public static GlobalBundleScanningSchemaServiceImpl createInstance(final BundleContext ctx) {
-        GlobalBundleScanningSchemaServiceImpl instance = new GlobalBundleScanningSchemaServiceImpl(ctx);
-        instance.start();
-        return instance;
+    private GlobalBundleScanningSchemaServiceImpl(final DOMSchemaService schemaService) {
+        this.schemaService = Preconditions.checkNotNull(schemaService);
+        this.yangProvider = (DOMYangTextSourceProvider) schemaService.getSupportedExtensions()
+                .get(DOMYangTextSourceProvider.class);
     }
 
-    public BundleContext getContext() {
-        return context;
-    }
-
-    private void start() {
-        checkState(context != null);
-        LOG.debug("start() starting");
-
-        listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, this);
-        bundleTracker = new BundleTracker<>(context, Bundle.RESOLVED | Bundle.STARTING |
-                Bundle.STOPPING | Bundle.ACTIVE, scanner);
-
-        synchronized (lock) {
-            bundleTracker.open();
-
-            LOG.debug("BundleTracker.open() complete");
-            if (Iterables.size(listeners.getListeners()) > 0) {
-                tryToUpdateSchemaContext();
-            }
-        }
-
-        listenerTracker.open();
-        starting = false;
-
-        LOG.debug("start() complete");
+    public static GlobalBundleScanningSchemaServiceImpl createInstance(final DOMSchemaService schemaService) {
+        return new GlobalBundleScanningSchemaServiceImpl(schemaService);
     }
 
     @Override
     public SchemaContext getSchemaContext() {
-        return getGlobalContext();
+        return schemaService.getGlobalContext();
     }
 
     @Override
     public SchemaContext getGlobalContext() {
-        return contextResolver.getSchemaContext().orNull();
+        return schemaService.getGlobalContext();
     }
 
     @Override
@@ -127,168 +76,44 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi
     public ListenerRegistration<SchemaContextListener> registerSchemaContextListener(
             final SchemaContextListener listener) {
         synchronized (lock) {
-            Optional<SchemaContext> potentialCtx = contextResolver.getSchemaContext();
-            if(potentialCtx.isPresent()) {
-                listener.onGlobalContextUpdated(potentialCtx.get());
-            }
-            return listeners.register(listener);
+            final ListenerRegistration<SchemaContextListener> reg = schemaService.registerSchemaContextListener(
+                listener);
+
+            final ListenerRegistration<SchemaContextListener> ret =
+                    new AbstractListenerRegistration<SchemaContextListener>(listener) {
+                @Override
+                protected void removeRegistration() {
+                    synchronized (lock) {
+                        listeners.remove(this);
+                    }
+                    reg.close();
+                }
+            };
+
+            listeners.add(ret);
+            return ret;
         }
     }
 
     @Override
     public void close() {
         synchronized (lock) {
-            stopping = true;
-            if (bundleTracker != null) {
-                bundleTracker.close();
-                bundleTracker = null;
-            }
-            if (listenerTracker != null) {
-                listenerTracker.close();
-                listenerTracker = null;
-            }
-
-            for (ListenerRegistration<SchemaContextListener> l : listeners.getListeners()) {
+            for (ListenerRegistration<?> l : listeners) {
                 l.close();
             }
+            listeners.clear();
         }
     }
 
-    @GuardedBy("lock")
-    private void notifyListeners(final SchemaContext snapshot) {
-        Object[] services = listenerTracker.getServices();
-        for (ListenerRegistration<SchemaContextListener> listener : listeners) {
-            try {
-                listener.getInstance().onGlobalContextUpdated(snapshot);
-            } catch (Exception e) {
-                LOG.error("Exception occured during invoking listener", e);
-            }
-        }
-        if (services != null) {
-            for (Object rawListener : services) {
-                final SchemaContextListener listener = (SchemaContextListener) rawListener;
-                try {
-                    listener.onGlobalContextUpdated(snapshot);
-                } catch (Exception e) {
-                    LOG.error("Exception occured during invoking listener {}", listener, e);
-                }
-            }
-        }
-    }
-
+    @SuppressWarnings("unchecked")
     @Override
     public CheckedFuture<YangTextSchemaSource, SchemaSourceException> getSource(
             final SourceIdentifier sourceIdentifier) {
-        return contextResolver.getSource(sourceIdentifier);
-    }
-
-    private class BundleScanner implements BundleTrackerCustomizer<Iterable<Registration>> {
-        @Override
-        public Iterable<Registration> addingBundle(final Bundle bundle, final BundleEvent event) {
-            if (bundle.getBundleId() == FRAMEWORK_BUNDLE_ID) {
-                return Collections.emptyList();
-            }
-
-            final Enumeration<URL> enumeration = bundle.findEntries("META-INF/yang", "*.yang", false);
-            if (enumeration == null) {
-                return Collections.emptyList();
-            }
-
-            final List<Registration> urls = new ArrayList<>();
-            while (enumeration.hasMoreElements()) {
-                final URL u = enumeration.nextElement();
-                try {
-                    urls.add(contextResolver.registerSource(u));
-                    LOG.debug("Registered {}", u);
-                } catch (Exception e) {
-                    LOG.warn("Failed to register {}, ignoring it", e);
-                }
-            }
-
-            if (!urls.isEmpty()) {
-                LOG.debug("Loaded {} new URLs from bundle {}, attempting to rebuild schema context",
-                        urls.size(), bundle.getSymbolicName());
-                tryToUpdateSchemaContext();
-            }
-
-            return ImmutableList.copyOf(urls);
-        }
-
-        @Override
-        public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> object) {
-            if (bundle.getBundleId() == FRAMEWORK_BUNDLE_ID) {
-                LOG.debug("Framework bundle {} got event {}", bundle, event.getType());
-                if ((event.getType() & BundleEvent.STOPPING) != 0) {
-                    LOG.info("OSGi framework is being stopped, halting bundle scanning");
-                    stopping = true;
-                }
-            }
-        }
-
-        /**
-         * If removing YANG files makes yang store inconsistent, method
-         * {@link #getYangStoreSnapshot()} will throw exception. There is no
-         * rollback.
-         */
-        @Override
-        public void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> urls) {
-            for (Registration url : urls) {
-                try {
-                    url.close();
-                } catch (Exception e) {
-                    LOG.warn("Failed do unregister URL {}, proceeding", url, e);
-                }
-            }
-
-            int numUrls = Iterables.size(urls);
-            if(numUrls > 0 ) {
-                if(LOG.isDebugEnabled()) {
-                    LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(), bundle.getState(),
-                        numUrls);
-                }
-
-                tryToUpdateSchemaContext();
-            }
+        if (yangProvider == null) {
+            return Futures.immediateFailedCheckedFuture(new MissingSchemaSourceException(
+                "Source provider is not available", sourceIdentifier));
         }
-    }
 
-    @Override
-    public SchemaContextListener addingService(final ServiceReference<SchemaContextListener> reference) {
-
-        SchemaContextListener listener = context.getService(reference);
-        SchemaContext _ctxContext = getGlobalContext();
-        if (getContext() != null && _ctxContext != null) {
-            listener.onGlobalContextUpdated(_ctxContext);
-        }
-        return listener;
-    }
-
-    public void tryToUpdateSchemaContext() {
-        if (starting || stopping) {
-            return;
-        }
-
-        synchronized (lock) {
-            Optional<SchemaContext> schema = contextResolver.getSchemaContext();
-            if (schema.isPresent()) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("Got new SchemaContext: # of modules {}", schema.get().getAllModuleIdentifiers().size());
-                }
-
-                notifyListeners(schema.get());
-            }
-        }
-    }
-
-    @Override
-    public void modifiedService(final ServiceReference<SchemaContextListener> reference,
-            final SchemaContextListener service) {
-        // NOOP
-    }
-
-    @Override
-    public void removedService(final ServiceReference<SchemaContextListener> reference,
-            final SchemaContextListener service) {
-        context.ungetService(reference);
+        return (CheckedFuture<YangTextSchemaSource, SchemaSourceException>) yangProvider.getSource(sourceIdentifier);
     }
 }
diff --git a/opendaylight/md-sal/sal-schema-service/src/main/java/org/opendaylight/controller/sal/schema/service/impl/SchemaServiceActivator.java b/opendaylight/md-sal/sal-schema-service/src/main/java/org/opendaylight/controller/sal/schema/service/impl/SchemaServiceActivator.java
deleted file mode 100644 (file)
index a13a872..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.controller.sal.schema.service.impl;
-
-import java.util.Hashtable;
-import org.opendaylight.controller.sal.core.api.model.SchemaService;
-import org.opendaylight.controller.sal.core.api.model.YangTextSourceProvider;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-public class SchemaServiceActivator implements BundleActivator {
-    private ServiceRegistration<SchemaService> schemaServiceReg;
-    private ServiceRegistration<YangTextSourceProvider> schemaSourceReg;
-    private GlobalBundleScanningSchemaServiceImpl schemaService;
-
-    @Override
-    public void start(final BundleContext context) {
-        schemaService = GlobalBundleScanningSchemaServiceImpl.createInstance(context);
-        schemaServiceReg = context.registerService(SchemaService.class, schemaService, new Hashtable<String,String>());
-        schemaSourceReg = context.registerService(YangTextSourceProvider.class, schemaService, new Hashtable<String,String>());
-    }
-
-    @Override
-    public void stop(final BundleContext context) {
-        schemaServiceReg.unregister();
-        schemaSourceReg.unregister();
-        schemaService.close();
-    }
-}
diff --git a/opendaylight/md-sal/sal-schema-service/src/main/resources/org/opendaylight/blueprint/sal-schema-service.xml b/opendaylight/md-sal/sal-schema-service/src/main/resources/org/opendaylight/blueprint/sal-schema-service.xml
new file mode 100644 (file)
index 0000000..a9a1bdb
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0">
+    <!-- MD-SAL's DOMSchemaService -->
+    <odl:static-reference id="domSchemaService" interface="org.opendaylight.mdsal.dom.api.DOMSchemaService"/>
+
+    <!-- Schema Service -->
+    <bean id="schemaService" class="org.opendaylight.controller.sal.schema.service.impl.GlobalBundleScanningSchemaServiceImpl"
+        factory-method="createInstance">
+        <argument ref="domSchemaService"/>
+    </bean>
+
+    <service ref="schemaService" odl:type="default">
+        <interfaces>
+            <value>org.opendaylight.controller.sal.core.api.model.SchemaService</value>
+            <value>org.opendaylight.controller.sal.core.api.model.YangTextSourceProvider</value>
+        </interfaces>
+    </service>
+</blueprint>