Author: cziegeler Date: Thu Nov 9 10:21:11 2017 New Revision: 1814713 URL: http://svn.apache.org/viewvc?rev=1814713&view=rev Log: FELIX-5739 : Strange behaviour with Lazy-ActivationPolicy and autostart
Added: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java (with props) Modified: felix/trunk/scr/bnd.bnd felix/trunk/scr/pom.xml felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/Activator.java felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ComponentRegistry.java felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ScrCommand.java Modified: felix/trunk/scr/bnd.bnd URL: http://svn.apache.org/viewvc/felix/trunk/scr/bnd.bnd?rev=1814713&r1=1814712&r2=1814713&view=diff ============================================================================== --- felix/trunk/scr/bnd.bnd (original) +++ felix/trunk/scr/bnd.bnd Thu Nov 9 10:21:11 2017 @@ -21,8 +21,7 @@ Export-Package: org.apache.felix.scr.com org.osgi.util.function;version=1.0, \ org.osgi.util.promise;version=1.0 -Private-Package: org.apache.felix.scr.impl.*, \ - org.apache.felix.utils.extender +Private-Package: org.apache.felix.scr.impl.* # Configuration Admin is optional and dynamic, but allow eager wiring by importing it # LogService is optional but if present the R4.0 version 1.3 is sufficient. Modified: felix/trunk/scr/pom.xml URL: http://svn.apache.org/viewvc/felix/trunk/scr/pom.xml?rev=1814713&r1=1814712&r2=1814713&view=diff ============================================================================== --- felix/trunk/scr/pom.xml (original) +++ felix/trunk/scr/pom.xml Thu Nov 9 10:21:11 2017 @@ -102,6 +102,18 @@ </dependency> <dependency> <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.log</artifactId> + <version>1.3.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.metatype</artifactId> + <version>1.3.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> <artifactId>org.osgi.namespace.extender</artifactId> <version>1.0.1</version> <scope>provided</scope> @@ -113,12 +125,6 @@ <scope>provided</scope> </dependency> <dependency> - <groupId>org.apache.felix</groupId> - <artifactId>org.apache.felix.utils</artifactId> - <version>1.8.6</version> - <scope>provided</scope> - </dependency> - <dependency> <groupId>${project.groupId}</groupId> <artifactId>org.apache.felix.shell</artifactId> <version>1.0.0</version> Added: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java?rev=1814713&view=auto ============================================================================== --- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java (added) +++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java Thu Nov 9 10:21:11 2017 @@ -0,0 +1,257 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.felix.scr.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.Constants; +import org.osgi.framework.SynchronousBundleListener; +import org.osgi.framework.startlevel.BundleStartLevel; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +/** + * Base class to write bundle extenders. + * This extender tracks started bundles (or starting if they have a lazy activation + * policy) and will create an extension for each of them to manage it. + * + * The extender will handle all concurrency and synchronization issues. + * + * The extender guarantee that all extensions will be stopped synchronously with + * the STOPPING event of a given bundle and that all extensions will be stopped + * before the extender bundle is stopped. + * + */ +public abstract class AbstractExtender implements BundleActivator, BundleTrackerCustomizer<Bundle>, SynchronousBundleListener { + + private final ConcurrentMap<Bundle, Activator.ScrExtension> extensions = new ConcurrentHashMap<Bundle, Activator.ScrExtension>(); + private final ConcurrentMap<Bundle, FutureTask<Void>> destroying = new ConcurrentHashMap<Bundle, FutureTask<Void>>(); + private volatile boolean stopping; + private volatile boolean stopped; + + private BundleContext context; + private BundleTracker<Bundle> tracker; + + public BundleContext getBundleContext() { + return context; + } + + public boolean isStopping() { + return stopping; + } + + @Override + public void start(BundleContext context) throws Exception { + this.context = context; + this.context.addBundleListener(this); + this.tracker = new BundleTracker<Bundle>(this.context, Bundle.ACTIVE | Bundle.STARTING, this); + doStart(); + } + + @Override + public void stop(BundleContext context) throws Exception { + stopping = true; + while (!extensions.isEmpty()) { + Collection<Bundle> toDestroy = chooseBundlesToDestroy(extensions.keySet()); + if (toDestroy == null || toDestroy.isEmpty()) { + toDestroy = new ArrayList<Bundle>(extensions.keySet()); + } + for (Bundle bundle : toDestroy) { + destroyExtension(bundle); + } + } + doStop(); + stopped = true; + } + + protected void doStart() throws Exception { + startTracking(); + } + + protected void doStop() throws Exception { + stopTracking(); + } + + protected void startTracking() { + this.tracker.open(); + } + + protected void stopTracking() { + this.tracker.close(); + } + + /** + * Create the executor used to start extensions asynchronously. + * + * @return an + */ + protected ExecutorService createExecutor() { + return Executors.newScheduledThreadPool(3); + } + + protected Collection<Bundle> chooseBundlesToDestroy(Set<Bundle> bundles) { + return null; + } + + + @Override + public void bundleChanged(BundleEvent event) { + if (stopped) { + return; + } + Bundle bundle = event.getBundle(); + if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) { + // The bundle is not in STARTING or ACTIVE state anymore + // so destroy the context. Ignore our own bundle since it + // needs to kick the orderly shutdown. + if (bundle != this.context.getBundle()) { + destroyExtension(bundle); + } + } + } + + @Override + public Bundle addingBundle(Bundle bundle, BundleEvent event) { + modifiedBundle(bundle, event, bundle); + return bundle; + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) { + if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) { + // The bundle is not in STARTING or ACTIVE state anymore + // so destroy the context. Ignore our own bundle since it + // needs to kick the orderly shutdown and not unregister the namespaces. + if (bundle != this.context.getBundle()) { + destroyExtension(bundle); + } + return; + } + // Do not track bundles given we are stopping + if (stopping) { + return; + } + // For starting bundles, ensure, it's a lazy activation, + // else we'll wait for the bundle to become ACTIVE + if (bundle.getState() == Bundle.STARTING) { + String activationPolicyHeader = bundle.getHeaders("").get(Constants.BUNDLE_ACTIVATIONPOLICY); + if (activationPolicyHeader == null + || !activationPolicyHeader.startsWith(Constants.ACTIVATION_LAZY) + || !bundle.adapt(BundleStartLevel.class).isActivationPolicyUsed()) { + // Do not track this bundle yet + return; + } + } + createExtension(bundle); + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) { + // Nothing to do + destroyExtension(bundle); + } + + private void createExtension(final Bundle bundle) { + try { + BundleContext bundleContext = bundle.getBundleContext(); + if (bundleContext == null) { + // The bundle has been stopped in the mean time + return; + } + final Activator.ScrExtension extension = doCreateExtension(bundle); + if (extension == null) { + // This bundle is not to be extended + return; + } + synchronized (extensions) { + if (extensions.putIfAbsent(bundle, extension) != null) { + return; + } + } + debug(bundle, "Starting extension synchronously"); + extension.start(); + } catch (Throwable t) { + warn(bundle, "Error while creating extension", t); + } + } + + private void destroyExtension(final Bundle bundle) { + FutureTask<Void> future; + synchronized (extensions) { + debug(bundle, "Starting destruction process"); + future = destroying.get(bundle); + if (future == null) { + final Activator.ScrExtension extension = extensions.remove(bundle); + if (extension != null) { + debug(bundle, "Scheduling extension destruction"); + future = new FutureTask<Void>(new Runnable() { + @Override + public void run() { + debug(bundle, "Destroying extension"); + try { + extension.destroy(); + } catch (Exception e) { + warn(bundle, "Error while destroying extension", e); + } finally { + debug(bundle, "Finished destroying extension"); + synchronized (extensions) { + destroying.remove(bundle); + } + } + } + }, null); + destroying.put(bundle, future); + } else { + debug(bundle, "Not an extended bundle or destruction of extension already finished"); + } + } else { + debug(bundle, "Destruction already scheduled"); + } + } + if (future != null) { + try { + debug(bundle, "Waiting for extension destruction"); + future.run(); + future.get(); + } catch (Throwable t) { + warn(bundle, "Error while destroying extension", t); + } + } + } + + /** + * Create the extension for the given bundle, or null if the bundle is not to be extended. + * + * @param bundle the bundle to extend + * @return The extension + * @throws Exception If something goes wrong + */ + protected abstract Activator.ScrExtension doCreateExtension(Bundle bundle) throws Exception; + + protected abstract void debug(Bundle bundle, String msg); + protected abstract void warn(Bundle bundle, String msg, Throwable t); +} Propchange: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/AbstractExtender.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/Activator.java URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/Activator.java?rev=1814713&r1=1814712&r2=1814713&view=diff ============================================================================== --- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/Activator.java (original) +++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/Activator.java Thu Nov 9 10:21:11 2017 @@ -31,8 +31,6 @@ import org.apache.felix.scr.impl.config. import org.apache.felix.scr.impl.helper.SimpleLogger; import org.apache.felix.scr.impl.inject.ClassUtils; import org.apache.felix.scr.impl.runtime.ServiceComponentRuntimeImpl; -import org.apache.felix.utils.extender.AbstractExtender; -import org.apache.felix.utils.extender.Extension; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; @@ -86,7 +84,6 @@ public class Activator extends AbstractE public Activator() { m_configuration = new ScrConfigurationImpl( this ); - setSynchronous( true ); } /** @@ -229,12 +226,12 @@ public class Activator extends AbstractE //---------- Component Management ----------------------------------------- @Override - protected Extension doCreateExtension(final Bundle bundle) throws Exception + protected Activator.ScrExtension doCreateExtension(final Bundle bundle) throws Exception { return new ScrExtension( bundle ); } - protected class ScrExtension implements Extension + protected class ScrExtension { private final Bundle bundle; @@ -463,18 +460,12 @@ public class Activator extends AbstractE } @Override - protected void error(String msg, Throwable t) - { - log( LogService.LOG_DEBUG, m_bundle, msg, t ); - } - - // @Override public void log(int level, String message, Throwable ex) { log( level, null, message, ex ); } - // @Override + @Override public void log(int level, String pattern, Object[] arguments, Throwable ex) { if ( isLogEnabled( level ) ) @@ -496,6 +487,7 @@ public class Activator extends AbstractE /** * Returns <code>true</code> if logging for the given level is enabled. */ + @Override public boolean isLogEnabled(int level) { return m_configuration == null || m_configuration.getLogLevel() >= level; Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ComponentRegistry.java URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ComponentRegistry.java?rev=1814713&r1=1814712&r2=1814713&view=diff ============================================================================== --- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ComponentRegistry.java (original) +++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ComponentRegistry.java Thu Nov 9 10:21:11 2017 @@ -41,7 +41,6 @@ import org.apache.felix.scr.impl.manager import org.apache.felix.scr.impl.metadata.ComponentMetadata; import org.apache.felix.scr.impl.metadata.TargetedPID; import org.osgi.framework.Bundle; -import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentConstants; @@ -444,48 +443,6 @@ public class ComponentRegistry //---------- Helper method - /** - * Returns <code>true</code> if the <code>bundle</code> is to be considered - * active from the perspective of declarative services. - * <p> - * As of R4.1 a bundle may have lazy activation policy which means a bundle - * remains in the STARTING state until a class is loaded from that bundle - * (unless that class is declared to not cause the bundle to start). And - * thus for DS 1.1 this means components are to be loaded for lazily started - * bundles being in the STARTING state (after the LAZY_ACTIVATION event) has - * been sent. Hence DS must consider a bundle active when it is really - * active and when it is a lazily activated bundle in the STARTING state. - * - * @param bundle The bundle check - * @return <code>true</code> if <code>bundle</code> is not <code>null</code> - * and the bundle is either active or has lazy activation policy - * and is in the starting state. - * - * @see <a href="https://issues.apache.org/jira/browse/FELIX-1666">FELIX-1666</a> - */ - static boolean isBundleActive( final Bundle bundle ) - { - if ( bundle != null ) - { - if ( bundle.getState() == Bundle.ACTIVE ) - { - return true; - } - - if ( bundle.getState() == Bundle.STARTING ) - { - // according to the spec the activationPolicy header is only - // set to request a bundle to be lazily activated. So in this - // simple check we just verify the header is set to assume - // the bundle is considered a lazily activated bundle - return bundle.getHeaders("").get(Constants.BUNDLE_ACTIVATIONPOLICY) != null; - } - } - - // fall back: bundle is not considered active - return false; - } - private final ThreadLocal<List<ServiceReference<?>>> circularInfos = new ThreadLocal<List<ServiceReference<?>>> () { @@ -588,6 +545,7 @@ public class ComponentRegistry Runnable runnable = new Runnable() { + @Override @SuppressWarnings("unchecked") public void run() { Modified: felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ScrCommand.java URL: http://svn.apache.org/viewvc/felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ScrCommand.java?rev=1814713&r1=1814712&r2=1814713&view=diff ============================================================================== --- felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ScrCommand.java (original) +++ felix/trunk/scr/src/main/java/org/apache/felix/scr/impl/ScrCommand.java Thu Nov 9 10:21:11 2017 @@ -58,6 +58,7 @@ public class ScrCommand implements ScrIn private static final Comparator<ComponentDescriptionDTO> DESCRIPTION_COMP = new Comparator<ComponentDescriptionDTO>() { + @Override public int compare(final ComponentDescriptionDTO c1, final ComponentDescriptionDTO c2) { final long bundleId1 = c1.bundle.id; @@ -85,6 +86,7 @@ public class ScrCommand implements ScrIn private static final Comparator<ComponentConfigurationDTO> CONFIGURATION_COMP = new Comparator<ComponentConfigurationDTO>() { + @Override public int compare(final ComponentConfigurationDTO c1, final ComponentConfigurationDTO c2) { return Long.signum(c1.id - c2.id); @@ -195,9 +197,52 @@ public class ScrCommand implements ScrIn } } + /** + * Returns <code>true</code> if the <code>bundle</code> is to be considered + * active from the perspective of declarative services. + * <p> + * As of R4.1 a bundle may have lazy activation policy which means a bundle + * remains in the STARTING state until a class is loaded from that bundle + * (unless that class is declared to not cause the bundle to start). And + * thus for DS 1.1 this means components are to be loaded for lazily started + * bundles being in the STARTING state (after the LAZY_ACTIVATION event) has + * been sent. Hence DS must consider a bundle active when it is really + * active and when it is a lazily activated bundle in the STARTING state. + * + * @param bundle The bundle check + * @return <code>true</code> if <code>bundle</code> is not <code>null</code> + * and the bundle is either active or has lazy activation policy + * and is in the starting state. + * + * @see <a href="https://issues.apache.org/jira/browse/FELIX-1666">FELIX-1666</a> + */ + private static boolean isBundleActive( final Bundle bundle ) + { + if ( bundle != null ) + { + if ( bundle.getState() == Bundle.ACTIVE ) + { + return true; + } + + if ( bundle.getState() == Bundle.STARTING ) + { + // according to the spec the activationPolicy header is only + // set to request a bundle to be lazily activated. So in this + // simple check we just verify the header is set to assume + // the bundle is considered a lazily activated bundle + return bundle.getHeaders("").get(Constants.BUNDLE_ACTIVATIONPOLICY) != null; + } + } + + // fall back: bundle is not considered active + return false; + } + /* (non-Javadoc) * @see org.apache.felix.scr.impl.ScrInfo#list(java.lang.String, java.io.PrintStream, java.io.PrintStream) */ + @Override public void list(final String bundleIdentifier, final PrintWriter out) { final List<ComponentDescriptionDTO> descriptions = new ArrayList<ComponentDescriptionDTO>(); @@ -228,7 +273,7 @@ public class ScrCommand implements ScrIn { throw new IllegalArgumentException("Missing bundle with ID " + bundleIdentifier); } - if (ComponentRegistry.isBundleActive(bundle)) + if (isBundleActive(bundle)) { descriptions.addAll(scrService.getComponentDescriptionDTOs(bundle)); if (descriptions.isEmpty()) @@ -287,6 +332,7 @@ public class ScrCommand implements ScrIn /** * @see org.apache.felix.scr.impl.ScrInfo#info(java.lang.String, java.io.PrintStream, java.io.PrintStream) */ + @Override public void info(final String componentId, final PrintWriter out) { final Result result = getComponentsFromArg(componentId, false); @@ -402,7 +448,7 @@ public class ScrCommand implements ScrIn { out.println(" (No Component Configurations)"); } - else + else { for (final ComponentConfigurationDTO cc: componentConfigurationDTOs) { @@ -546,6 +592,7 @@ public class ScrCommand implements ScrIn /** * @see org.apache.felix.scr.impl.ScrInfo#config(java.io.PrintStream) */ + @Override public void config(final PrintWriter out) { out.print("Log Level: ");