blob: 01abd706aa9e709b35558960f38244a1cdc20d5c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.framework;
import java.security.ProtectionDomain;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.osgi.container.Module;
import org.eclipse.osgi.container.Module.Settings;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.container.ModuleCollisionHook;
import org.eclipse.osgi.container.ModuleContainerAdaptor;
import org.eclipse.osgi.container.ModuleLoader;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.container.ModuleRevisionBuilder;
import org.eclipse.osgi.container.ModuleWiring;
import org.eclipse.osgi.container.SystemModule;
import org.eclipse.osgi.internal.container.AtomicLazyInitializer;
import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.eclipse.osgi.internal.loader.FragmentLoader;
import org.eclipse.osgi.internal.loader.SystemBundleLoader;
import org.eclipse.osgi.internal.permadmin.BundlePermissions;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.Storage;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.wiring.BundleRevision;
public class EquinoxContainerAdaptor extends ModuleContainerAdaptor {
private final EquinoxContainer container;
private final Storage storage;
private final OSGiFrameworkHooks hooks;
private final Map<Long, Generation> initial;
// The ClassLoader parent to use when creating ModuleClassLoaders.
private final ClassLoader moduleClassLoaderParent;
private final AtomicLong lastSecurityAdminFlush;
final AtomicLazyInitializer<Executor> resolverExecutor;
final Callable<Executor> lazyResolverExecutorCreator;
final AtomicLazyInitializer<Executor> startLevelExecutor;
final Callable<Executor> lazyStartLevelExecutorCreator;
public EquinoxContainerAdaptor(EquinoxContainer container, Storage storage, Map<Long, Generation> initial) {
this.container = container;
this.storage = storage;
this.hooks = new OSGiFrameworkHooks(container, storage);
this.initial = initial;
this.moduleClassLoaderParent = getModuleClassLoaderParent(container.getConfiguration(), container.getBootLoader());
this.lastSecurityAdminFlush = new AtomicLong();
EquinoxConfiguration config = container.getConfiguration();
@SuppressWarnings("deprecation")
String resolverThreadCntProp = config.getConfiguration(EquinoxConfiguration.PROP_EQUINOX_RESOLVER_THREAD_COUNT, //
config.getConfiguration(EquinoxConfiguration.PROP_RESOLVER_THREAD_COUNT));
int resolverThreadCnt;
try {
// note that resolver thread count defaults to -1 (compute based on processor number)
resolverThreadCnt = resolverThreadCntProp == null ? -1 : Integer.parseInt(resolverThreadCntProp);
} catch (NumberFormatException e) {
resolverThreadCnt = -1;
}
String startLevelThreadCntProp = config.getConfiguration(EquinoxConfiguration.PROP_EQUINOX_START_LEVEL_THREAD_COUNT);
int startLevelThreadCnt;
try {
// Note that start-level thread count defaults to 1 (synchronous start)
startLevelThreadCnt = startLevelThreadCntProp == null ? 1 : Integer.parseInt(startLevelThreadCntProp);
} catch (NumberFormatException e) {
startLevelThreadCnt = 1;
}
// Use two different executors for resolver and start-level because of the different queue requirements
// For the resolver we must use a SynchronousQueue because multiple threads
// can kick off a resolution operation and block one of the executor threads
// per resolution operation.
// If the number of concurrent resolution operations reaches the number of
// executor threads then each executor thread may end up blocked causing the
// executor to no longer accept work. A SynchronousQueue prevents that from
// happening.
this.resolverExecutor = new AtomicLazyInitializer<>();
this.lazyResolverExecutorCreator = createLazyExecutorCreator( //
"Equinox resolver thread - " + EquinoxContainerAdaptor.this.toString(), //$NON-NLS-1$
resolverThreadCnt, new SynchronousQueue<>());
// For the start-level we can safely use a growing queue because the thread feeding the
// start-level executor with work is a single thread and it can safely block waiting
// for the work of the executor threads to finish.
this.startLevelExecutor = new AtomicLazyInitializer<>();
this.lazyStartLevelExecutorCreator = createLazyExecutorCreator(//
"Equinox start level thread - " + EquinoxContainerAdaptor.this.toString(), //$NON-NLS-1$
startLevelThreadCnt, new LinkedBlockingQueue<>(1000));
}
private Callable<Executor> createLazyExecutorCreator(final String threadName, int threadCnt, final BlockingQueue<Runnable> queue) {
// use the number of processors when configured value is <=0
final int maxThreads = threadCnt <= 0 ? Runtime.getRuntime().availableProcessors() : threadCnt;
return () -> {
if (maxThreads == 1) {
// just do synchronous execution with current thread
return Runnable::run;
}
// Always want to create core threads until max size
int coreThreads = maxThreads;
// idle timeout; make it short to get rid of threads quickly after use
int idleTimeout = 10;
// try to name the threads with useful name
ThreadFactory threadFactory = r -> {
Thread t = new Thread(r, threadName);
t.setDaemon(true);
return t;
};
// use a rejection policy that simply runs the task in the current thread once the max pool size is reached
RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.CallerRunsPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(coreThreads, maxThreads, idleTimeout, TimeUnit.SECONDS, queue, threadFactory, rejectHandler);
executor.allowCoreThreadTimeOut(true);
return executor;
};
}
private static ClassLoader getModuleClassLoaderParent(EquinoxConfiguration configuration, ClassLoader bootLoader) {
// allow hooks to determine the parent class loader
for (ClassLoaderHook hook : configuration.getHookRegistry().getClassLoaderHooks()) {
ClassLoader parent = hook.getModuleClassLoaderParent(configuration);
if (parent != null) {
// first one to return non-null wins.
return parent;
}
}
// DEFAULT behavior:
// check property for specified parent
// check the osgi defined property first
String type = configuration.getConfiguration(Constants.FRAMEWORK_BUNDLE_PARENT);
if (type == null) {
type = configuration.getConfiguration(EquinoxConfiguration.PROP_PARENT_CLASSLOADER, Constants.FRAMEWORK_BUNDLE_PARENT_BOOT);
}
if (Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK.equalsIgnoreCase(type) || EquinoxConfiguration.PARENT_CLASSLOADER_FWK.equalsIgnoreCase(type)) {
ClassLoader cl = EquinoxContainer.class.getClassLoader();
return cl == null ? bootLoader : cl;
}
if (Constants.FRAMEWORK_BUNDLE_PARENT_APP.equalsIgnoreCase(type))
return ClassLoader.getSystemClassLoader();
if (Constants.FRAMEWORK_BUNDLE_PARENT_EXT.equalsIgnoreCase(type)) {
ClassLoader appCL = ClassLoader.getSystemClassLoader();
if (appCL != null)
return appCL.getParent();
}
return bootLoader;
}
@Override
public ModuleCollisionHook getModuleCollisionHook() {
return hooks.getModuleCollisionHook();
}
@Override
public ResolverHookFactory getResolverHookFactory() {
return hooks.getResolverHookFactory();
}
@Override
public void publishContainerEvent(ContainerEvent type, Module module, Throwable error, FrameworkListener... listeners) {
EquinoxEventPublisher publisher = container.getEventPublisher();
if (publisher != null) {
publisher.publishFrameworkEvent(getType(type), module.getBundle(), error, listeners);
}
}
@Override
public void publishModuleEvent(ModuleEvent type, Module module, Module origin) {
EquinoxEventPublisher publisher = container.getEventPublisher();
if (publisher != null) {
publisher.publishBundleEvent(getType(type), module.getBundle(), origin.getBundle());
}
}
@Override
public Module createModule(String location, long id, EnumSet<Settings> settings, int startlevel) {
EquinoxBundle bundle = new EquinoxBundle(id, location, storage.getModuleContainer(), settings, startlevel, container);
return bundle.getModule();
}
@Override
public SystemModule createSystemModule() {
return (SystemModule) new EquinoxBundle.SystemBundle(storage.getModuleContainer(), container).getModule();
}
@Override
public String getProperty(String key) {
return storage.getConfiguration().getConfiguration(key);
}
@Override
public ModuleLoader createModuleLoader(ModuleWiring wiring) {
if (wiring.getBundle().getBundleId() == 0) {
ClassLoader cl = EquinoxContainer.class.getClassLoader();
cl = cl == null ? container.getBootLoader() : cl;
return new SystemBundleLoader(wiring, container, cl);
}
if ((wiring.getRevision().getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
return new FragmentLoader();
}
return new BundleLoader(wiring, container, moduleClassLoaderParent);
}
@Override
public Generation getRevisionInfo(String location, long id) {
return initial.remove(id);
}
@Override
public void associateRevision(ModuleRevision revision, Object revisionInfo) {
((Generation) revisionInfo).setRevision(revision);
}
@Override
public void invalidateWiring(ModuleWiring moduleWiring, ModuleLoader current) {
if (current instanceof BundleLoader) {
BundleLoader bundleLoader = (BundleLoader) current;
bundleLoader.close();
}
long updatedTimestamp = storage.getModuleDatabase().getRevisionsTimestamp();
if (System.getSecurityManager() != null && updatedTimestamp != lastSecurityAdminFlush.getAndSet(updatedTimestamp)) {
storage.getSecurityAdmin().clearCaches();
List<Module> modules = storage.getModuleContainer().getModules();
for (Module module : modules) {
for (ModuleRevision revision : module.getRevisions().getModuleRevisions()) {
Generation generation = (Generation) revision.getRevisionInfo();
if (generation != null) {
ProtectionDomain domain = generation.getDomain(false);
if (domain != null) {
((BundlePermissions) domain.getPermissions()).clearPermissionCache();
}
}
}
}
}
clearManifestCache(moduleWiring);
}
private void clearManifestCache(ModuleWiring moduleWiring) {
boolean frameworkActive = Module.ACTIVE_SET.contains(storage.getModuleContainer().getModule(0).getState());
ModuleRevision revision = moduleWiring.getRevision();
Module module = revision.getRevisions().getModule();
boolean isUninstallingOrUninstalled = State.UNINSTALLED.equals(module.getState()) ^ module.holdsTransitionEventLock(ModuleEvent.UNINSTALLED);
if (!frameworkActive || !isUninstallingOrUninstalled) {
// only do this when the framework is not active or when the bundle is not uninstalled
Generation generation = (Generation) moduleWiring.getRevision().getRevisionInfo();
generation.clearManifestCache();
}
}
static int getType(ContainerEvent type) {
switch (type) {
case ERROR :
return FrameworkEvent.ERROR;
case INFO :
return FrameworkEvent.INFO;
case WARNING :
return FrameworkEvent.WARNING;
case REFRESH :
return FrameworkEvent.PACKAGES_REFRESHED;
case START_LEVEL :
return FrameworkEvent.STARTLEVEL_CHANGED;
case STARTED :
return FrameworkEvent.STARTED;
case STOPPED :
return FrameworkEvent.STOPPED;
case STOPPED_REFRESH :
return FrameworkEvent.STOPPED_SYSTEM_REFRESHED;
case STOPPED_UPDATE :
return FrameworkEvent.STOPPED_UPDATE;
case STOPPED_TIMEOUT :
return FrameworkEvent.WAIT_TIMEDOUT;
default :
// default to error
return FrameworkEvent.ERROR;
}
}
private int getType(ModuleEvent type) {
switch (type) {
case INSTALLED :
return BundleEvent.INSTALLED;
case LAZY_ACTIVATION :
return BundleEvent.LAZY_ACTIVATION;
case RESOLVED :
return BundleEvent.RESOLVED;
case STARTED :
return BundleEvent.STARTED;
case STARTING :
return BundleEvent.STARTING;
case STOPPING :
return BundleEvent.STOPPING;
case STOPPED :
return BundleEvent.STOPPED;
case UNINSTALLED :
return BundleEvent.UNINSTALLED;
case UNRESOLVED :
return BundleEvent.UNRESOLVED;
case UPDATED :
return BundleEvent.UPDATED;
default :
// TODO log error?
return 0;
}
}
@Override
public void refreshedSystemModule() {
storage.getConfiguration().setConfiguration(EquinoxConfiguration.PROP_FORCED_RESTART, "true"); //$NON-NLS-1$
}
@Override
public String toString() {
return container.toString();
}
@Override
public void updatedDatabase() {
StorageSaver saver = container.getStorageSaver();
if (saver == null)
return;
saver.save();
}
@Override
public void initBegin() {
hooks.initBegin();
}
@Override
public void initEnd() {
hooks.initEnd();
}
@Override
public DebugOptions getDebugOptions() {
return container.getConfiguration().getDebugOptions();
}
@Override
public Executor getResolverExecutor() {
return resolverExecutor.getInitialized(lazyResolverExecutorCreator);
}
@Override
public Executor getStartLevelExecutor() {
return startLevelExecutor.getInitialized(lazyStartLevelExecutorCreator);
}
@Override
public ScheduledExecutorService getScheduledExecutor() {
return container.getScheduledExecutor();
}
public void shutdownExecutors() {
Executor current = resolverExecutor.getAndClear();
if (current instanceof ExecutorService) {
((ExecutorService) current).shutdown();
}
current = startLevelExecutor.getAndClear();
if (current instanceof ExecutorService) {
((ExecutorService) current).shutdown();
}
}
@Override
public ModuleRevisionBuilder adaptModuleRevisionBuilder(ModuleEvent operation, Module origin, ModuleRevisionBuilder builder, Object revisionInfo) {
Generation generation = (Generation) revisionInfo;
return generation.adaptModuleRevisionBuilder(operation, origin, builder);
}
}