Return-Path: X-Original-To: apmail-aries-commits-archive@www.apache.org Delivered-To: apmail-aries-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 594B718298 for ; Thu, 28 Jan 2016 19:28:02 +0000 (UTC) Received: (qmail 80070 invoked by uid 500); 28 Jan 2016 19:27:59 -0000 Delivered-To: apmail-aries-commits-archive@aries.apache.org Received: (qmail 79995 invoked by uid 500); 28 Jan 2016 19:27:59 -0000 Mailing-List: contact commits-help@aries.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aries.apache.org Delivered-To: mailing list commits@aries.apache.org Received: (qmail 79984 invoked by uid 99); 28 Jan 2016 19:27:58 -0000 Received: from Unknown (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 28 Jan 2016 19:27:58 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 70EA2C3283 for ; Thu, 28 Jan 2016 19:27:58 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.8 X-Spam-Level: * X-Spam-Status: No, score=1.8 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-0.001, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-us-west.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id LVMQ4tQZohWp for ; Thu, 28 Jan 2016 19:27:50 +0000 (UTC) Received: from mailrelay1-us-west.apache.org (mailrelay1-us-west.apache.org [209.188.14.139]) by mx1-us-west.apache.org (ASF Mail Server at mx1-us-west.apache.org) with ESMTP id A842F206F3 for ; Thu, 28 Jan 2016 19:27:50 +0000 (UTC) Received: from svn01-us-west.apache.org (svn.apache.org [10.41.0.6]) by mailrelay1-us-west.apache.org (ASF Mail Server at mailrelay1-us-west.apache.org) with ESMTP id 4A8D7E0185 for ; Thu, 28 Jan 2016 19:27:50 +0000 (UTC) Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id 48E5D3A00E9 for ; Thu, 28 Jan 2016 19:27:50 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1727424 - in /aries/trunk/subsystem: subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/ subsystem-itests/src/test/java/org/apache/aries/subsystem/... Date: Thu, 28 Jan 2016 19:27:50 -0000 To: commits@aries.apache.org From: jwross@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20160128192750.48E5D3A00E9@svn01-us-west.apache.org> Author: jwross Date: Thu Jan 28 19:27:49 2016 New Revision: 1727424 URL: http://svn.apache.org/viewvc?rev=1727424&view=rev Log: [ARIES-1383] Provide option to disable the provisioning of dependencies at install time. As part of the implemented locking strategy, three locks are used. The Global Read/Write Lock (GRWL) is used to ensure thread safety among all operations: install, install dependencies, resolve, start, stop, and uninstall. The write lock is acquired for install, install dependencies, and uninstall. The read lock is acquired for resolve, start, and stop. The Global Mutual Exlusion Lock (GMEL) is used to prevent cycle deadlocks when acquiring the state change locks of individual subsystems. Every subsystem has a Local State Change Lock (LSCL). These locks are used to prevent more than one state change operation at a time to occur for the same subsystem. A condition exists for the GMEL. The condition is used in order to notify waiting threads that the LSCL of at least one subsystem was released and that at least one thread may now be able to proceed. Threads that fail to acquire the LSCL of a subsystem while holding the GMEL will release all currently held locks and wait for the condition to apply. This is necessary because, when starting, the target subsystem must be locked before any other affected subsystems since the latter may only be computed after dependencies, which may include other subsystems, are installed. The consequences of this strategy is that installs and uninstalls are synchronous while resolutions, starts, and stops are asynchronous as long as the same subsystem is not affected. It may be possible to create an even more granular locking mechanism in the future if dictated by performance requirements. Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/BasicSubsystem.java Thu Jan 28 19:27:49 2016 @@ -32,6 +32,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; import org.apache.aries.subsystem.AriesSubsystem; import org.apache.aries.subsystem.core.archive.AriesProvisionDependenciesDirective; @@ -621,6 +622,11 @@ public class BasicSubsystem implements R } } + private final ReentrantLock stateChangeLock = new ReentrantLock(); + ReentrantLock stateChangeLock() { + return stateChangeLock; + } + private String getDeploymentManifestHeaderValue(String name) { DeploymentManifest manifest = getDeploymentManifest(); if (manifest == null) @@ -754,8 +760,8 @@ public class BasicSubsystem implements R } } - void computeDependenciesPostInstallation() throws IOException { - resource.computeDependencies(null); + void computeDependenciesPostInstallation(Coordination coordination) throws IOException { + resource.computeDependencies(null, coordination); ProvisionResourceHeader header = resource.computeProvisionResourceHeader(); setDeploymentManifest( new DeploymentManifest.Builder() Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/InstallAction.java Thu Jan 28 19:27:49 2016 @@ -41,57 +41,68 @@ public class InstallAction implements Pr @Override public BasicSubsystem run() { - State state = parent.getState(); - if (State.INSTALLING.equals(state)) { - throw new SubsystemException("A child subsystem may not be installed while the parent is in the INSTALLING state"); - } - // Initialization of a null coordination must be privileged and, - // therefore, occur in the run() method rather than in the constructor. - Coordination coordination = Utils.createCoordination(parent); + // Doesn't appear to be any need of protecting against re-entry in the + // case of installation. BasicSubsystem result = null; + // Acquire the global write lock to prevent all other operations until + // the installation is complete. There is no need to hold any other locks. + LockingStrategy.writeLock(); try { - TargetRegion region = new TargetRegion(parent); - SubsystemResource ssr = new SubsystemResource(location, content, parent); - result = Activator.getInstance().getSubsystems().getSubsystemByLocation(location); - if (result != null) { - checkLifecyclePermission(result); - if (!region.contains(result)) - throw new SubsystemException("Location already exists but existing subsystem is not part of target region: " + location); - if (!(result.getSymbolicName().equals(ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName()) - && result.getVersion().equals(ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion()) - && result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType()))) - throw new SubsystemException("Location already exists but symbolic name, version, and type are not the same: " + location); - return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install(); + State state = parent.getState(); + if (State.INSTALLING.equals(state)) { + throw new SubsystemException("A child subsystem may not be installed while the parent is in the INSTALLING state"); } - result = (BasicSubsystem)region.find( - ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(), - ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion()); - if (result != null) { + // Initialization of a null coordination must be privileged and, + // therefore, occur in the run() method rather than in the constructor. + Coordination coordination = Utils.createCoordination(parent); + try { + TargetRegion region = new TargetRegion(parent); + SubsystemResource ssr = new SubsystemResource(location, content, parent, coordination); + result = Activator.getInstance().getSubsystems().getSubsystemByLocation(location); + if (result != null) { + if (!region.contains(result)) + throw new SubsystemException("Location already exists but existing subsystem is not part of target region: " + location); + if (!(result.getSymbolicName().equals(ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName()) + && result.getVersion().equals(ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion()) + && result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType()))) + throw new SubsystemException("Location already exists but symbolic name, version, and type are not the same: " + location); + } + else { + result = (BasicSubsystem)region.find( + ssr.getSubsystemManifest().getSubsystemSymbolicNameHeader().getSymbolicName(), + ssr.getSubsystemManifest().getSubsystemVersionHeader().getVersion()); + if (result != null) { + if (!result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType())) + throw new SubsystemException("Subsystem already exists in target region but has a different type: " + location); + } + else { + result = new BasicSubsystem(ssr, deploymentManifest); + } + } checkLifecyclePermission(result); - if (!result.getType().equals(ssr.getSubsystemManifest().getSubsystemTypeHeader().getType())) - throw new SubsystemException("Subsystem already exists in target region but has a different type: " + location); return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install(); } - result = new BasicSubsystem(ssr, deploymentManifest); - checkLifecyclePermission(result); - return (BasicSubsystem)ResourceInstaller.newInstance(coordination, result, parent).install(); - } - catch (Throwable t) { - coordination.fail(t); - } - finally { - try { - coordination.end(); + catch (Throwable t) { + coordination.fail(t); } - catch (CoordinationException e) { - Throwable t = e.getCause(); - if (t instanceof SubsystemException) - throw (SubsystemException)t; - if (t instanceof SecurityException) - throw (SecurityException)t; - throw new SubsystemException(t); + finally { + try { + coordination.end(); + } + catch (CoordinationException e) { + Throwable t = e.getCause(); + if (t instanceof SubsystemException) + throw (SubsystemException)t; + if (t instanceof SecurityException) + throw (SecurityException)t; + throw new SubsystemException(t); + } } } + finally { + // Release the global write lock. + LockingStrategy.writeUnlock(); + } return result; } Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java?rev=1727424&view=auto ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java (added) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/LockingStrategy.java Thu Jan 28 19:27:49 2016 @@ -0,0 +1,164 @@ +package org.apache.aries.subsystem.core.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.osgi.service.subsystem.Subsystem; +import org.osgi.service.subsystem.SubsystemException; + +public class LockingStrategy { + private static final int TRY_LOCK_TIME = 30000; + private static final TimeUnit TRY_LOCK_TIME_UNIT = TimeUnit.MILLISECONDS; + + /* + * A mutual exclusion lock used when acquiring the state change locks of + * a collection of subsystems in order to prevent cycle deadlocks. + */ + private static final ReentrantLock lock = new ReentrantLock(); + /* + * Used when the state change lock of a subsystem cannot be acquired. All + * other state change locks are released while waiting. The condition is met + * whenever the state change lock of one or more subsystems is released. + */ + private static final Condition condition = lock.newCondition(); + + /* + * Allow only one of the following operations to be executing at the same + * time. + * + * (1) Install + * (2) Install Dependencies + * (3) Uninstall + * + * Allow any number of the following operations to be executing at the same + * time. + * + * (1) Resolve + * (2) Start + * (3) Stop + */ + private static final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); + + private static final ThreadLocal>> local = new ThreadLocal>>() { + @Override + protected Map> initialValue() { + return new HashMap>(); + } + }; + + public static void lock() { + try { + if (!lock.tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) { + throw new SubsystemException("Unable to acquire the global mutual exclusion lock in time."); + } + } + catch (InterruptedException e) { + throw new SubsystemException(e); + } + } + + public static void unlock() { + lock.unlock(); + } + + public static void lock(Collection subsystems) { + Collection locked = new ArrayList(subsystems.size()); + try { + while (locked.size() < subsystems.size()) { + for (BasicSubsystem subsystem : subsystems) { + if (!subsystem.stateChangeLock().tryLock()) { + unlock(locked); + locked.clear(); + if (!LockingStrategy.condition.await(TRY_LOCK_TIME, TimeUnit.SECONDS)) { + throw new SubsystemException("Unable to acquire the state change lock in time: " + subsystem); + } + break; + } + locked.add(subsystem); + } + } + } + catch (InterruptedException e) { + unlock(locked); + throw new SubsystemException(e); + } + } + + public static void unlock(Collection subsystems) { + for (BasicSubsystem subsystem : subsystems) { + subsystem.stateChangeLock().unlock(); + } + signalAll(); + } + + private static void signalAll() { + lock(); + try { + condition.signalAll(); + } + finally { + unlock(); + } + } + + public static boolean set(Subsystem.State state, BasicSubsystem subsystem) { + Map> map = local.get(); + Set subsystems = map.get(state); + if (subsystems == null) { + subsystems = new HashSet(); + map.put(state, subsystems); + local.set(map); + } + if (subsystems.contains(subsystem)) { + return false; + } + subsystems.add(subsystem); + return true; + } + + public static void unset(Subsystem.State state, BasicSubsystem subsystem) { + Map> map = local.get(); + Set subsystems = map.get(state); + if (subsystems != null) { + subsystems.remove(subsystem); + } + } + + public static void readLock() { + try { + if (!rwlock.readLock().tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) { + throw new SubsystemException("Unable to acquire the global read lock in time."); + } + } + catch (InterruptedException e) { + throw new SubsystemException(e); + } + } + + public static void readUnlock() { + rwlock.readLock().unlock(); + } + + public static void writeLock() { + try { + if (!rwlock.writeLock().tryLock(TRY_LOCK_TIME, TRY_LOCK_TIME_UNIT)) { + throw new SubsystemException("Unable to acquire the global write lock in time."); + } + } + catch (InterruptedException e) { + throw new SubsystemException(e); + } + } + + public static void writeUnlock() { + rwlock.writeLock().unlock(); + } +} Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResolveContext.java Thu Jan 28 19:27:49 2016 @@ -15,6 +15,7 @@ package org.apache.aries.subsystem.core. import java.io.IOException; import java.net.URISyntaxException; +import java.security.AccessController; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,6 +28,7 @@ import org.apache.aries.subsystem.core.a import org.apache.aries.subsystem.core.archive.SubsystemTypeHeader; import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent; import org.apache.aries.subsystem.core.internal.DependencyCalculator.MissingCapability; +import org.apache.aries.subsystem.core.internal.StartAction.Restriction; import org.apache.aries.subsystem.core.repository.Repository; import org.eclipse.equinox.region.Region; import org.osgi.framework.BundleException; @@ -42,6 +44,7 @@ import org.osgi.resource.Requirement; import org.osgi.resource.Resource; import org.osgi.resource.Wiring; import org.osgi.service.resolver.HostedCapability; +import org.osgi.service.subsystem.Subsystem.State; import org.osgi.service.subsystem.SubsystemException; public class ResolveContext extends org.osgi.service.resolver.ResolveContext { @@ -61,9 +64,40 @@ public class ResolveContext extends org. repositoryServiceRepository = new RepositoryServiceRepository(); systemRepository = Activator.getInstance().getSystemRepository(); } + + private void installDependenciesOfRequirerIfNecessary(Requirement requirement) { + if (requirement == null) { + return; + } + Resource requirer = requirement.getResource(); + if (resource.equals(requirer)) { + return; + } + Collection subsystems; + if (requirer instanceof BasicSubsystem) { + BasicSubsystem subsystem = (BasicSubsystem)requirer; + subsystems = Collections.singletonList(subsystem); + } + else if (requirer instanceof BundleRevision) { + BundleRevision revision = (BundleRevision)requirer; + BundleConstituent constituent = new BundleConstituent(null, revision); + subsystems = Activator.getInstance().getSubsystems().getSubsystemsByConstituent(constituent); + } + else { + return; + } + for (BasicSubsystem subsystem : subsystems) { + if (Utils.isProvisionDependenciesInstall(subsystem) + || !State.INSTALLING.equals(subsystem.getState())) { + continue; + } + AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, Restriction.INSTALL_ONLY)); + } + } @Override public List findProviders(Requirement requirement) { + installDependenciesOfRequirerIfNecessary(requirement); ArrayList result = new ArrayList(); try { // Only check the system repository for osgi.ee and osgi.native @@ -160,7 +194,8 @@ public class ResolveContext extends org. } private boolean addDependenciesFromSystemRepository(Requirement requirement, List capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException { - return addDependencies(systemRepository, requirement, capabilities, true); + boolean result = addDependencies(systemRepository, requirement, capabilities, true); + return result; } private void addValidCapabilities(Collection from, Collection to, Requirement requirement, boolean validate) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException { Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java Thu Jan 28 19:27:49 2016 @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; @@ -57,155 +57,283 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StartAction extends AbstractAction { - private static final Logger logger = LoggerFactory.getLogger(StartAction.class); + public static enum Restriction { + NONE, + INSTALL_ONLY, + RESOLVE_ONLY + } - private static final ThreadLocal> subsystemsStartingOnCurrentThread = new ThreadLocal>() { - @Override - protected Set initialValue() { - return new HashSet(); - } - }; + private static final Logger logger = LoggerFactory.getLogger(StartAction.class); private final Coordination coordination; private final BasicSubsystem instigator; - private final boolean resolveOnly; + private final Restriction restriction; public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target) { - this(instigator, requestor, target, false); + this(instigator, requestor, target, Restriction.NONE); } - public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, boolean resolveOnly) { - this(instigator, requestor, target, null, resolveOnly); + public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Restriction restriction) { + this(instigator, requestor, target, null, restriction); } public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination) { - this(instigator, requestor, target, coordination, false); + this(instigator, requestor, target, coordination, Restriction.NONE); } - public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination, boolean resolveOnly) { + public StartAction(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target, Coordination coordination, Restriction restriction) { super(requestor, target, false); this.instigator = instigator; this.coordination = coordination; - this.resolveOnly = resolveOnly; + this.restriction = restriction; } - private Object doRun() { - // TODO We now support circular dependencies so a sane locking strategy - // is required now more than ever before. Needs to be much more granular - // (and complex) than this. Perhaps something along the lines of a state - // change lock per subsystem then use a global lock only while acquiring - // the necessary state change locks. - synchronized (StartAction.class) { - State state = target.getState(); - // The following states are illegal. - if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED, State.UNINSTALLING).contains(state)) - throw new SubsystemException("Cannot start from state " + state); - // The following states must wait with the exception of INSTALLING - // combined with apache-aries-provision-dependencies:=resolve. - if ((State.INSTALLING.equals(state) - && Utils.isProvisionDependenciesInstall(target)) - || EnumSet.of(State.RESOLVING, State.STARTING, State.STOPPING).contains(state)) { - waitForStateChange(state); - return new StartAction(instigator, requestor, target, coordination).run(); - } - // The following states mean the requested state has already been attained. - if (State.ACTIVE.equals(state)) - return null; - // Always start if target is content of requestor. - if (!Utils.isContent(requestor, target)) { - // Always start if target is a dependency of requestor. - if (!Utils.isDependency(requestor, target)) { - // Always start if instigator equals target (explicit start). - if (!instigator.equals(target)) { - // Don't start if instigator is root (restart) and target is not ready. - if (instigator.isRoot() && !target.isReadyToStart()) { - return null; - } - } - } - } - Coordination coordination = this.coordination; - if (coordination == null) { - coordination = Utils.createCoordination(target); - } - try { - // If necessary, install the dependencies. - if (State.INSTALLING.equals(target.getState()) && - !Utils.isProvisionDependenciesInstall(target)) { - // The following line is necessary in order to ensure that - // the export sharing policies of composites are in place - // for capability validation. - setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(coordination); - Collection subsystems = new ArrayList(); - subsystems.addAll(Activator.getInstance().getSubsystems().getChildren(target)); - subsystems.addAll(target.getParents()); - for (Subsystem subsystem : subsystems) { - if (State.INSTALLING.equals(subsystem.getState())) { - BasicSubsystem bs = (BasicSubsystem)subsystem; - bs.computeDependenciesPostInstallation(); - new InstallDependencies().install(bs, null, coordination); - bs.setState(State.INSTALLED); - } - } - target.computeDependenciesPostInstallation(); - new InstallDependencies().install(target, null, coordination); - target.setState(State.INSTALLED); - } - // Resolve if necessary. - if (State.INSTALLED.equals(target.getState())) - resolve(target, coordination); - if (resolveOnly) - return null; - target.setState(State.STARTING); - // TODO Need to hold a lock here to guarantee that another start - // operation can't occur when the state goes to RESOLVED. - // Start the subsystem. - List resources = new ArrayList(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target)); - SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader(); - if (header != null) - Collections.sort(resources, new StartResourceComparator(header)); - for (Resource resource : resources) - startResource(resource, coordination); - target.setState(State.ACTIVE); - } catch (Throwable t) { - coordination.fail(t); - // TODO Need to reinstate complete isolation by disconnecting the - // region and transition to INSTALLED. - } finally { - try { - // Don't end the coordination if the subsystem being started - // (i.e. the target) did not begin it. - if (coordination.getName().equals(Utils.computeCoordinationName(target))) - coordination.end(); - } catch (CoordinationException e) { - // If the target's state is INSTALLING then installing the - // dependencies failed, in which case we want to leave it as is. - if (!State.INSTALLING.equals(target.getState())) { - target.setState(State.RESOLVED); - } - Throwable t = e.getCause(); - if (t instanceof SubsystemException) - throw (SubsystemException)t; - throw new SubsystemException(t); - } - } - return null; + private static boolean isTargetStartable(BasicSubsystem instigator, BasicSubsystem requestor, BasicSubsystem target) { + State state = target.getState(); + // The following states are illegal. + if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED).contains(state)) + throw new SubsystemException("Cannot start from state " + state); + // The following states mean the requested state has already been attained. + if (State.ACTIVE.equals(state)) + return false; + // Always start if target is content of requestor. + if (!Utils.isContent(requestor, target)) { + // Always start if target is a dependency of requestor. + if (!Utils.isDependency(requestor, target)) { + // Always start if instigator equals target (explicit start). + if (!instigator.equals(target)) { + // Don't start if instigator is root (restart) and target is not ready. + if (instigator.isRoot() && !target.isReadyToStart()) { + return false; + } + } + } + } + return true; + } + + private void installDependencies(BasicSubsystem target, Coordination coordination) throws Exception { + for (Subsystem parent : target.getParents()) { + AccessController.doPrivileged(new StartAction(instigator, target, (BasicSubsystem)parent, coordination, Restriction.INSTALL_ONLY)); + } + installDependencies(Collections.singletonList(target), coordination); + for (Subsystem child : Activator.getInstance().getSubsystems().getChildren(target)) { + AccessController.doPrivileged(new StartAction(instigator, target, (BasicSubsystem)child, coordination, Restriction.INSTALL_ONLY)); + } + } + + private static void installDependencies(Collection subsystems, Coordination coordination) throws Exception { + for (Subsystem subsystem : subsystems) { + if (State.INSTALLING.equals(subsystem.getState())) { + BasicSubsystem bs = (BasicSubsystem)subsystem; + bs.computeDependenciesPostInstallation(coordination); + new InstallDependencies().install(bs, null, coordination); + bs.setState(State.INSTALLED); + } + } + } + + private Coordination createCoordination() { + Coordination coordination = this.coordination; + if (coordination == null) { + coordination = Utils.createCoordination(target); + } + return coordination; + } + + private static LinkedHashSet computeAffectedSubsystems(BasicSubsystem target) { + LinkedHashSet result = new LinkedHashSet(); + Subsystems subsystems = Activator.getInstance().getSubsystems(); + for (Resource dep : subsystems.getResourcesReferencedBy(target)) { + if (dep instanceof BasicSubsystem + && !subsystems.getChildren(target).contains(dep)) { + result.add((BasicSubsystem)dep); + } + else if (dep instanceof BundleRevision) { + BundleConstituent constituent = new BundleConstituent(null, (BundleRevision)dep); + if (!target.getConstituents().contains(constituent)) { + for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent( + new BundleConstituent(null, (BundleRevision)dep))) { + result.add(constituentOf); + } + } + } } + for (Subsystem child : subsystems.getChildren(target)) { + result.add((BasicSubsystem)child); + } + for (Resource resource : target.getResource().getSharedContent()) { + for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent( + resource instanceof BundleRevision ? new BundleConstituent(null, (BundleRevision)resource) : resource)) { + result.add(constituentOf); + } + } + result.add(target); + return result; } @Override public Object run() { - Set subsystems = subsystemsStartingOnCurrentThread.get(); - if (subsystems.contains(target)) { + // Protect against re-entry now that cycles are supported. + if (!LockingStrategy.set(State.STARTING, target)) { return null; } - subsystems.add(target); - try { - return doRun(); - } - finally { - subsystems.remove(target); - } + try { + Collection subsystems; + // We are now protected against re-entry. + // If necessary, install the dependencies. + if (State.INSTALLING.equals(target.getState()) && !Utils.isProvisionDependenciesInstall(target)) { + // Acquire the global write lock while installing dependencies. + LockingStrategy.writeLock(); + try { + // We are now protected against installs, starts, stops, and uninstalls. + // We need a separate coordination when installing + // dependencies because cleaning up the temporary export + // sharing policies must be done while holding the write lock. + Coordination c = Utils.createCoordination(target); + try { + installDependencies(target, c); + // Associated subsystems must be computed after all dependencies + // are installed because some of the dependencies may be + // subsystems. This is safe to do while only holding the read + // lock since we know that nothing can be added or removed. + subsystems = computeAffectedSubsystems(target); + for (BasicSubsystem subsystem : subsystems) { + if (State.INSTALLING.equals(subsystem.getState()) + && !Utils.isProvisionDependenciesInstall(subsystem)) { + installDependencies(subsystem, c); + } + } + // Downgrade to the read lock in order to prevent + // installs and uninstalls but allow starts and stops. + LockingStrategy.readLock(); + } + catch (Throwable t) { + c.fail(t); + } + finally { + // This will clean up the temporary export sharing + // policies. Must be done while holding the write lock. + c.end(); + } + } + finally { + // Release the global write lock as soon as possible. + LockingStrategy.writeUnlock(); + } + } + else { + // Acquire the read lock in order to prevent installs and + // uninstalls but allow starts and stops. + LockingStrategy.readLock(); + } + try { + // We now hold the read lock and are protected against installs + // and uninstalls. + if (Restriction.INSTALL_ONLY.equals(restriction)) { + return null; + } + // Compute associated subsystems here in case (1) they weren't + // computed previously while holding the write lock or (2) they + // were computed previously and more were subsequently added. + // This is safe to do while only holding the read lock since we + // know that nothing can be added or removed. + subsystems = computeAffectedSubsystems(target); + // Acquire the global mutual exclusion lock while acquiring the + // state change locks of affected subsystems. + LockingStrategy.lock(); + try { + // We are now protected against cycles. + // Acquire the state change locks of affected subsystems. + LockingStrategy.lock(subsystems); + } + finally { + // Release the global mutual exclusion lock as soon as possible. + LockingStrategy.unlock(); + } + Coordination coordination = this.coordination; + try { + coordination = createCoordination(); + // We are now protected against other starts and stops of the affected subsystems. + if (!isTargetStartable(instigator, requestor, target)) { + return null; + } + + // Resolve if necessary. + if (State.INSTALLED.equals(target.getState())) + resolve(instigator, target, target, coordination, subsystems); + if (Restriction.RESOLVE_ONLY.equals(restriction)) + return null; + target.setState(State.STARTING); + // Be sure to set the state back to RESOLVED if starting fails. + coordination.addParticipant(new Participant() { + @Override + public void ended(Coordination coordination) throws Exception { + // Nothing. + } + + @Override + public void failed(Coordination coordination) throws Exception { + target.setState(State.RESOLVED); + } + }); + for (BasicSubsystem subsystem : subsystems) { + if (!target.equals(subsystem)) { + startSubsystemResource(subsystem, coordination); + } + } + List resources = new ArrayList(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target)); + SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader(); + if (header != null) + Collections.sort(resources, new StartResourceComparator(header)); + for (Resource resource : resources) + startResource(resource, coordination); + target.setState(State.ACTIVE); + + } + catch (Throwable t) { + // We catch exceptions and fail the coordination here to + // ensure we are still holding the state change locks when + // the participant sets the state to RESOLVED. + coordination.fail(t); + } + finally { + try { + // Don't end a coordination that was not begun as part + // of this start action. + if (coordination.getName().equals(Utils.computeCoordinationName(target))) { + coordination.end(); + } + } + finally { + // Release the state change locks of affected subsystems. + LockingStrategy.unlock(subsystems); + } + } + } + finally { + // Release the read lock. + LockingStrategy.readUnlock(); + } + } + catch (CoordinationException e) { + Throwable t = e.getCause(); + if (t == null) { + throw new SubsystemException(e); + } + if (t instanceof SecurityException) { + throw (SecurityException)t; + } + if (t instanceof SubsystemException) { + throw (SubsystemException)t; + } + throw new SubsystemException(t); + } + finally { + // Protection against re-entry no longer required. + LockingStrategy.unset(State.STARTING, target); + } + return null; } private static Collection getBundles(BasicSubsystem subsystem) { @@ -233,41 +361,21 @@ public class StartAction extends Abstrac subsystem.setState(State.RESOLVED); } - private static void resolveSubsystems(BasicSubsystem subsystem, Coordination coordination) { - //resolve dependencies to ensure framework resolution succeeds - Subsystems subsystems = Activator.getInstance().getSubsystems(); - for (Resource dep : subsystems.getResourcesReferencedBy(subsystem)) { - if (dep instanceof BasicSubsystem - && !subsystems.getChildren(subsystem).contains(dep)) { - resolveSubsystem((BasicSubsystem)dep, coordination); - } - else if (dep instanceof BundleRevision - && !subsystem.getConstituents().contains(dep)) { - for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent( - new BundleConstituent(null, (BundleRevision)dep))) { - resolveSubsystem(constituentOf, coordination); - } - } - } - for (Subsystem child : subsystems.getChildren(subsystem)) { - resolveSubsystem((BasicSubsystem)child, coordination); - } - for (Resource resource : subsystem.getResource().getSharedContent()) { - for (BasicSubsystem constituentOf : subsystems.getSubsystemsByConstituent( - resource instanceof BundleRevision ? new BundleConstituent(null, (BundleRevision)resource) : resource)) { - resolveSubsystem(constituentOf, coordination); - } + private static void resolveSubsystems(BasicSubsystem instigator, BasicSubsystem target, Coordination coordination, Collection subsystems) throws Exception { + for (BasicSubsystem subsystem : subsystems) { + resolveSubsystem(instigator, target, subsystem, coordination); } } - private static void resolveSubsystem(BasicSubsystem subsystem, Coordination coordination) { + private static void resolveSubsystem(BasicSubsystem instigator, BasicSubsystem target, BasicSubsystem subsystem, Coordination coordination) throws Exception { State state = subsystem.getState(); if (State.INSTALLED.equals(state)) { - AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, coordination, true)); - } - else if (State.INSTALLING.equals(state) - && !Utils.isProvisionDependenciesInstall(subsystem)) { - AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, coordination, true)); + if (target.equals(subsystem)) { + resolve(instigator, target, subsystem, coordination, Collections.emptyList()); + } + else { + AccessController.doPrivileged(new StartAction(instigator, target, subsystem, coordination, Restriction.RESOLVE_ONLY)); + } } } @@ -282,7 +390,7 @@ public class StartAction extends Abstrac } } - private static void resolve(BasicSubsystem subsystem, Coordination coordination) { + private static void resolve(BasicSubsystem instigator, BasicSubsystem target, BasicSubsystem subsystem, Coordination coordination, Collection subsystems) { emitResolvingEvent(subsystem); try { // The root subsystem should follow the same event pattern for @@ -291,7 +399,7 @@ public class StartAction extends Abstrac // actually doing the resolution work. if (!subsystem.isRoot()) { setExportIsolationPolicy(subsystem, coordination); - resolveSubsystems(subsystem, coordination); + resolveSubsystems(instigator, target, coordination, subsystems); resolveBundles(subsystem); } emitResolvedEvent(subsystem); @@ -491,8 +599,11 @@ public class StartAction extends Abstrac return false; } - private void startSubsystemResource(Resource resource, Coordination coordination) throws IOException { + private void startSubsystemResource(Resource resource, final Coordination coordination) throws IOException { final BasicSubsystem subsystem = (BasicSubsystem)resource; + if (!isTargetStartable(instigator, target, subsystem)) { + return; + } // Subsystems that are content resources of another subsystem must have // their autostart setting set to started. if (Utils.isContent(this.target, subsystem)) @@ -560,7 +671,7 @@ public class StartAction extends Abstrac logger.error(diagnostics.toString()); } - private static void setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(Coordination coordination) throws InvalidSyntaxException { + static void setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(Coordination coordination) throws InvalidSyntaxException { for (BasicSubsystem subsystem : Activator.getInstance().getSubsystems().getSubsystems()) { if (!State.INSTALLING.equals(subsystem.getState()) || Utils.isProvisionDependenciesInstall(subsystem)) { Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java Thu Jan 28 19:27:49 2016 @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedHashSet; import java.util.List; import org.apache.aries.subsystem.ContentHandler; @@ -42,54 +43,102 @@ public class StopAction extends Abstract @Override public Object run() { - checkRoot(); - State state = target.getState(); - if (EnumSet.of(State.INSTALLED, State.RESOLVED).contains(state)) + // Protect against re-entry now that cycles are supported. + if (!LockingStrategy.set(State.STOPPING, target)) { return null; - else if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLING, State.UNINSTALLED).contains(state)) - throw new IllegalStateException("Cannot stop from state " + state); - else if (State.INSTALLING.equals(state) && !Utils.isProvisionDependenciesInstall(target)) { - return null; - } - else if (EnumSet.of(State.INSTALLING, State.RESOLVING, State.STARTING, State.STOPPING).contains(state)) { - waitForStateChange(state); - return new StopAction(requestor, target, disableRootCheck).run(); } - target.setState(State.STOPPING); - List resources = new ArrayList(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target)); - SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader(); - if (header != null) { - Collections.sort(resources, new StartResourceComparator(target.getSubsystemManifest().getSubsystemContentHeader())); - Collections.reverse(resources); - } - for (Resource resource : resources) { - // Don't stop the region context bundle. - if (Utils.isRegionContextBundle(resource)) - continue; + try { + // We are now protected against re-entry. + // Acquire the global read lock to prevent installs and uninstalls + // but allow starts and stops. + LockingStrategy.readLock(); try { - stopResource(resource); + // We are now protected against installs and uninstalls. + checkRoot(); + // Compute affected subsystems. This is safe to do while only + // holding the read lock since we know that nothing can be added + // or removed. + LinkedHashSet subsystems = new LinkedHashSet(); + subsystems.add(target); + List resources = new ArrayList(Activator.getInstance().getSubsystems().getResourcesReferencedBy(target)); + for (Resource resource : resources) { + if (resource instanceof BasicSubsystem) { + subsystems.add((BasicSubsystem)resource); + } + } + // Acquire the global mutual exclusion lock while acquiring the + // state change locks of affected subsystems. + LockingStrategy.lock(); + try { + // We are now protected against cycles. + // Acquire the state change locks of affected subsystems. + LockingStrategy.lock(subsystems); + } + finally { + // Release the global mutual exclusion lock as soon as possible. + LockingStrategy.unlock(); + } + try { + // We are now protected against other starts and stops of the affected subsystems. + State state = target.getState(); + if (EnumSet.of(State.INSTALLED, State.INSTALLING, State.RESOLVED).contains(state)) { + // INSTALLING is included because a subsystem may + // persist in this state without being locked when + // apache-aries-provision-dependencies:=resolve. + return null; + } + else if (EnumSet.of(State.INSTALL_FAILED, State.UNINSTALLED).contains(state)) { + throw new IllegalStateException("Cannot stop from state " + state); + } + target.setState(State.STOPPING); + SubsystemContentHeader header = target.getSubsystemManifest().getSubsystemContentHeader(); + if (header != null) { + Collections.sort(resources, new StartResourceComparator(target.getSubsystemManifest().getSubsystemContentHeader())); + Collections.reverse(resources); + } + for (Resource resource : resources) { + // Don't stop the region context bundle. + if (Utils.isRegionContextBundle(resource)) + continue; + try { + stopResource(resource); + } + catch (Exception e) { + logger.error("An error occurred while stopping resource " + resource + " of subsystem " + target, e); + } + } + // TODO Can we automatically assume it actually is resolved? + target.setState(State.RESOLVED); + try { + synchronized (target) { + target.setDeploymentManifest(new DeploymentManifest( + target.getDeploymentManifest(), + null, + target.isAutostart(), + target.getSubsystemId(), + SubsystemIdentifier.getLastId(), + target.getLocation(), + false, + false)); + } + } + catch (Exception e) { + throw new SubsystemException(e); + } + } + finally { + // Release the state change locks of affected subsystems. + LockingStrategy.unlock(subsystems); + } } - catch (Exception e) { - logger.error("An error occurred while stopping resource " + resource + " of subsystem " + target, e); - } - } - // TODO Can we automatically assume it actually is resolved? - target.setState(State.RESOLVED); - try { - synchronized (target) { - target.setDeploymentManifest(new DeploymentManifest( - target.getDeploymentManifest(), - null, - target.isAutostart(), - target.getSubsystemId(), - SubsystemIdentifier.getLastId(), - target.getLocation(), - false, - false)); + finally { + // Release the read lock. + LockingStrategy.readUnlock(); } } - catch (Exception e) { - throw new SubsystemException(e); + finally { + // Protection against re-entry no longer required. + LockingStrategy.unset(State.STOPPING, target); } return null; } Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java Thu Jan 28 19:27:49 2016 @@ -87,18 +87,18 @@ public class SubsystemResource implement private final Collection sharedContent = new HashSet(); private final Collection sharedDependencies = new HashSet(); - public SubsystemResource(String location, IDirectory content, BasicSubsystem parent) throws URISyntaxException, IOException, ResolutionException, BundleException, InvalidSyntaxException { - this(new RawSubsystemResource(location, content, parent), parent); + public SubsystemResource(String location, IDirectory content, BasicSubsystem parent, Coordination coordination) throws URISyntaxException, IOException, ResolutionException, BundleException, InvalidSyntaxException { + this(new RawSubsystemResource(location, content, parent), parent, coordination); } - public SubsystemResource(RawSubsystemResource resource, BasicSubsystem parent) throws IOException, BundleException, InvalidSyntaxException, URISyntaxException { + public SubsystemResource(RawSubsystemResource resource, BasicSubsystem parent, Coordination coordination) throws IOException, BundleException, InvalidSyntaxException, URISyntaxException { this.parent = parent; this.resource = resource; computeContentResources(resource.getDeploymentManifest()); capabilities = computeCapabilities(); if (this.getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isInstall()) { /* compute dependencies now only if we intend to provision them during install */ - computeDependencies(resource.getDeploymentManifest()); + computeDependencies(resource.getDeploymentManifest(), coordination); } deploymentManifest = computeDeploymentManifest(); } @@ -122,7 +122,7 @@ public class SubsystemResource implement capabilities = computeCapabilities(); if (getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isInstall()) { /* compute dependencies if we intend to provision them during install */ - computeDependencies(resource.getDeploymentManifest()); + computeDependencies(resource.getDeploymentManifest(), null); } } @@ -367,9 +367,9 @@ public class SubsystemResource implement } } - void computeDependencies(DeploymentManifest manifest) { + void computeDependencies(DeploymentManifest manifest, Coordination coordination) { if (manifest == null) { - computeDependencies(getSubsystemManifest()); + computeDependencies(getSubsystemManifest(), coordination); } else { ProvisionResourceHeader header = manifest.getProvisionResourceHeader(); @@ -384,9 +384,13 @@ public class SubsystemResource implement } } - private void computeDependencies(SubsystemManifest manifest) { + private void computeDependencies(SubsystemManifest manifest, Coordination coordination) { SubsystemContentHeader contentHeader = manifest.getSubsystemContentHeader(); try { + // The following line is necessary in order to ensure that the + // export sharing policies of composites are in place for capability + // validation. + StartAction.setExportPolicyOfAllInstallingSubsystemsWithProvisionDependenciesResolve(coordination); Map> resolution = Activator.getInstance().getResolver().resolve(createResolveContext()); setImportIsolationPolicy(resolution); for (Map.Entry> entry : resolution.entrySet()) { Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java Thu Jan 28 19:27:49 2016 @@ -83,15 +83,15 @@ public class SubsystemResourceInstaller } Comparator comparator = new InstallResourceComparator(); // Install dependencies first if appropriate... - if (Utils.isProvisionDependenciesInstall(subsystem)) { - new InstallDependencies().install(subsystem, this.subsystem, coordination); + if (!subsystem.isRoot() && Utils.isProvisionDependenciesInstall(subsystem)) { + new InstallDependencies().install(subsystem, this.subsystem, coordination); } // ...followed by content. // Simulate installation of shared content so that necessary relationships are established. - for (Resource content : subsystem.getResource().getSharedContent()) { - ResourceInstaller.newInstance(coordination, content, subsystem).install(); - } - // Now take care of the installable content. + for (Resource content : subsystem.getResource().getSharedContent()) { + ResourceInstaller.newInstance(coordination, content, subsystem).install(); + } + // Now take care of the installable content. if (State.INSTALLING.equals(subsystem.getState())) { List installableContent = new ArrayList(subsystem.getResource().getInstallableContent()); Collections.sort(installableContent, comparator); @@ -101,7 +101,7 @@ public class SubsystemResourceInstaller // Only brand new subsystems should have acquired the INSTALLING state, // in which case an INSTALLED event must be propagated. if (State.INSTALLING.equals(subsystem.getState()) && - Utils.isProvisionDependenciesInstall(subsystem)) { + Utils.isProvisionDependenciesInstall(subsystem)) { subsystem.setState(State.INSTALLED); } else { @@ -112,7 +112,7 @@ public class SubsystemResourceInstaller } private BasicSubsystem installRawSubsystemResource(RawSubsystemResource resource) throws Exception { - SubsystemResource subsystemResource = new SubsystemResource(resource, provisionTo); + SubsystemResource subsystemResource = new SubsystemResource(resource, provisionTo, coordination); return installSubsystemResource(subsystemResource); } Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/UninstallAction.java Thu Jan 28 19:27:49 2016 @@ -24,22 +24,35 @@ public class UninstallAction extends Abs @Override public Object run() { - checkValid(); - checkRoot(); - State state = target.getState(); - if (EnumSet.of(State.UNINSTALLED).contains(state)) + // Protect against re-entry now that cycles are supported. + if (!LockingStrategy.set(State.STOPPING, target)) { return null; - else if ((State.INSTALLING.equals(state) && Utils.isProvisionDependenciesInstall(target)) - || EnumSet.of(State.RESOLVING, State.STARTING, State.STOPPING, State.UNINSTALLING).contains(state)) { - waitForStateChange(state); - target.uninstall(); } - else if (state.equals(State.ACTIVE)) { - new StopAction(requestor, target, disableRootCheck).run(); - target.uninstall(); + try { + // Acquire the global write lock to prevent all other operations until + // the installation is complete. There is no need to hold any other locks. + LockingStrategy.writeLock(); + try { + checkRoot(); + checkValid(); + State state = target.getState(); + if (EnumSet.of(State.UNINSTALLED).contains(state)) { + return null; + } + if (state.equals(State.ACTIVE)) { + new StopAction(requestor, target, disableRootCheck).run(); + } + ResourceUninstaller.newInstance(requestor, target).uninstall(); + } + finally { + // Release the global write lock. + LockingStrategy.writeUnlock(); + } + } + finally { + // Protection against re-entry no longer required. + LockingStrategy.unset(State.STOPPING, target); } - else - ResourceUninstaller.newInstance(requestor, target).uninstall(); return null; } } Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java (original) +++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/WovenClassListener.java Thu Jan 28 19:27:49 2016 @@ -30,6 +30,7 @@ import java.util.Set; import org.apache.aries.subsystem.core.archive.DynamicImportPackageHeader; import org.apache.aries.subsystem.core.archive.DynamicImportPackageRequirement; import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent; +import org.apache.aries.subsystem.core.internal.StartAction.Restriction; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionDigraph.FilteredRegion; import org.eclipse.equinox.region.RegionDigraphVisitor; @@ -111,7 +112,7 @@ public class WovenClassListener implemen // package imports to the sharing policy in order to minimize // unpredictable wirings. Resolving the scoped subsystem will also // resolve all of the unscoped subsystems in the region. - AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, true)); + AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, Restriction.RESOLVE_ONLY)); } Bundle systemBundle = context.getBundle(org.osgi.framework.Constants.SYSTEM_BUNDLE_LOCATION); FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class); Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java (original) +++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/defect/Aries1383Test.java Thu Jan 28 19:27:49 2016 @@ -1443,7 +1443,7 @@ public class Aries1383Test extends Subsy assertConstituent(f1, "b2"); startSubsystem(f1, false); stoppableSubsystems.add(f1); - assertState(State.RESOLVED, f2); + assertState(EnumSet.of(State.RESOLVED, State.ACTIVE), f2); assertConstituent(s1, "b4"); assertConstituent(s1, "b5"); assertConstituent(s1, "b6"); @@ -1821,7 +1821,33 @@ public class Aries1383Test extends Subsy public static class TestServiceImpl implements TestService {} - public static class TestServiceActivator implements BundleActivator { + public static class TestServiceClientActivator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + ServiceReference ref = null; + for (int i = 0; i < 80; i++) { // 20 seconds with 250ms sleep. + ref = context.getServiceReference(TestService.class); + if (ref == null) { + Thread.sleep(250); + continue; + } + break; + } + try { + TestService service = context.getService(ref); + service.getClass(); + } + finally { + context.ungetService(ref); + } + } + + @Override + public void stop(BundleContext context) throws Exception { + } + } + + public static class TestServiceImplActivator implements BundleActivator { private ServiceRegistration reg; @Override @@ -2309,7 +2335,7 @@ public class Aries1383Test extends Subsy .importPackage("org.osgi.framework") .requireBundle("api") .clazz(TestServiceImpl.class) - .activator(TestServiceActivator.class) + .activator(TestServiceImplActivator.class) .build()) .build(), false); @@ -2397,4 +2423,297 @@ public class Aries1383Test extends Subsy startFutures.get(1).get(); startFutures.get(2).get(); } + + @Test + public void testComApiComImplComClient() throws Exception { + Subsystem root = getRootSubsystem(); + final Subsystem shared = installSubsystem( + root, + "shared", + new SubsystemArchiveBuilder() + .symbolicName("shared") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE + + ';' + + AriesProvisionDependenciesDirective.RESOLVE.toString() + + ';' + + SubsystemConstants.PROVISION_POLICY_DIRECTIVE + + ":=" + + SubsystemConstants.PROVISION_POLICY_ACCEPT_DEPENDENCIES) + .importPackage("org.osgi.framework") + .build(), + false + ); + uninstallableSubsystems.add(shared); + shared.start(); + stoppableSubsystems.add(shared); + @SuppressWarnings("unchecked") + Callable[] installCallables = new Callable[] { + new Callable() { + @Override + public Subsystem call() throws Exception { + Subsystem result = installSubsystem( + shared, + "client", + new SubsystemArchiveBuilder() + .symbolicName("client") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE) + .content("client;version=\"[0,0]\"") + .importPackage("org.osgi.framework") + .requireCapability("osgi.service;filter:=\"(objectClass=" + + TestService.class.getName() + + ")\";effective:=active") + .importService(TestService.class.getName()) + .requireBundle("api,impl") + .bundle( + "client", + new BundleArchiveBuilder() + .symbolicName("client") + .importPackage("org.osgi.framework") + .requireCapability("osgi.service;filter:=\"(objectClass=" + + TestService.class.getName() + + ")\";effective:=active") + .requireBundle("api,impl") + .activator(TestServiceClientActivator.class) + .build()) + .build(), + false); + return result; + } + + }, + new Callable() { + @Override + public Subsystem call() throws Exception { + Subsystem result = installSubsystem( + shared, + "impl", + new SubsystemArchiveBuilder() + .symbolicName("impl") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE) + .content("impl;version=\"[0,0]\"") + .provideCapability("osgi.service;objectClass:List=\"" + + TestService.class.getName() + + "\"") + .exportService(TestService.class.getName()) + .importPackage("org.osgi.framework") + .requireBundle("api") + .provideCapability("osgi.wiring.bundle;osgi.wiring.bundle=impl;bundle-version=0") + .bundle( + "impl", + new BundleArchiveBuilder() + .symbolicName("impl") + .provideCapability("osgi.service;objectClass:List=\"" + + TestService.class.getName() + + "\"") + .importPackage("org.osgi.framework") + .requireBundle("api") + .clazz(TestServiceImpl.class) + .activator(TestServiceImplActivator.class) + .build()) + .build(), + false); + return result; + } + + }, + new Callable() { + @Override + public Subsystem call() throws Exception { + Subsystem result = installSubsystem( + shared, + "api", + new SubsystemArchiveBuilder() + .symbolicName("api") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE) + .content("api;version=\"[0,0]\"") + .exportPackage("org.apache.aries.subsystem.itests.defect") + .provideCapability("osgi.wiring.bundle;osgi.wiring.bundle=api;bundle-version=0") + .bundle( + "api", + new BundleArchiveBuilder() + .symbolicName("api") + .exportPackage("org.apache.aries.subsystem.itests.defect") + .clazz(TestService.class) + .build()) + .build(), + false); + return result; + } + + } + }; + ExecutorService executor = Executors.newFixedThreadPool(3); + List> installFutures = executor.invokeAll(Arrays.asList(installCallables)); + final Subsystem client = installFutures.get(0).get(); + final Subsystem impl = installFutures.get(1).get(); + final Subsystem api = installFutures.get(2).get(); + @SuppressWarnings("unchecked") + Callable[] startCallables = new Callable[] { + new Callable() { + @Override + public Void call() throws Exception { + client.start(); + assertEvent(client, State.INSTALLED, subsystemEvents.poll(client.getSubsystemId(), 5000)); + assertEvent(client, State.RESOLVING, subsystemEvents.poll(client.getSubsystemId(), 5000)); + assertEvent(client, State.RESOLVED, subsystemEvents.poll(client.getSubsystemId(), 5000)); + assertEvent(client, State.STARTING, subsystemEvents.poll(client.getSubsystemId(), 5000)); + assertEvent(client, State.ACTIVE, subsystemEvents.poll(client.getSubsystemId(), 5000)); + return null; + } + }, + new Callable() { + @Override + public Void call() throws Exception { + impl.start(); + assertEvent(impl, State.INSTALLED, subsystemEvents.poll(impl.getSubsystemId(), 5000)); + assertEvent(impl, State.RESOLVING, subsystemEvents.poll(impl.getSubsystemId(), 5000)); + assertEvent(impl, State.RESOLVED, subsystemEvents.poll(impl.getSubsystemId(), 5000)); + assertEvent(impl, State.STARTING, subsystemEvents.poll(impl.getSubsystemId(), 5000)); + assertEvent(impl, State.ACTIVE, subsystemEvents.poll(impl.getSubsystemId(), 5000)); + return null; + } + }, + new Callable() { + @Override + public Void call() throws Exception { + api.start(); + assertEvent(api, State.INSTALLED, subsystemEvents.poll(api.getSubsystemId(), 5000)); + assertEvent(api, State.RESOLVING, subsystemEvents.poll(api.getSubsystemId(), 5000)); + assertEvent(api, State.RESOLVED, subsystemEvents.poll(api.getSubsystemId(), 5000)); + assertEvent(api, State.STARTING, subsystemEvents.poll(api.getSubsystemId(), 5000)); + assertEvent(api, State.ACTIVE, subsystemEvents.poll(api.getSubsystemId(), 5000)); + return null; + } + } + }; + List> startFutures = executor.invokeAll(Arrays.asList(startCallables)); + startFutures.get(0).get(); + startFutures.get(1).get(); + startFutures.get(2).get(); + } + + @Test + public void testAutoInstallDependenciesComposite() throws Exception { + Subsystem root = getRootSubsystem(); + Subsystem b = installSubsystem( + root, + "b", + new SubsystemArchiveBuilder() + .symbolicName("b") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE + + ';' + + AriesProvisionDependenciesDirective.RESOLVE.toString()) + .content("a;version=\"[0,0]\"") + .exportPackage("a") + .importPackage("b") + .bundle( + "a", + new BundleArchiveBuilder() + .symbolicName("a") + .importPackage("b") + .exportPackage("a") + .build()) + .bundle( + "b", + new BundleArchiveBuilder() + .symbolicName("b") + .exportPackage("b") + .build()) + .build(), + false + ); + uninstallableSubsystems.add(b); + try { + Subsystem a = installSubsystem( + root, + "a", + new SubsystemArchiveBuilder() + .symbolicName("a") + .type(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION) + .bundle( + "a", + new BundleArchiveBuilder() + .symbolicName("a") + .importPackage("a") + .build()) + .build(), + true + ); + uninstallableSubsystems.add(a); + assertState(EnumSet.of(State.INSTALLED, State.RESOLVED), b); + } + catch (Exception e) { + e.printStackTrace(); + fail("Subsystem should have installed"); + } + } + + @Test + public void testAutoInstallDependenciesFeature() throws Exception { + Subsystem root = getRootSubsystem(); + Subsystem shared = installSubsystem( + root, + "shared", + new SubsystemArchiveBuilder() + .symbolicName("shared") + .type(SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE + + ';' + + AriesProvisionDependenciesDirective.RESOLVE.toString() + + ';' + + SubsystemConstants.PROVISION_POLICY_DIRECTIVE + + ":=" + + SubsystemConstants.PROVISION_POLICY_ACCEPT_DEPENDENCIES) + .build(), + false + ); + uninstallableSubsystems.add(shared); + startSubsystem(shared, false); + Subsystem b = installSubsystem( + shared, + "b", + new SubsystemArchiveBuilder() + .symbolicName("b") + .type(SubsystemConstants.SUBSYSTEM_TYPE_FEATURE) + .content("a") + .bundle( + "a", + new BundleArchiveBuilder() + .symbolicName("a") + .importPackage("b") + .exportPackage("a") + .build()) + .bundle( + "b", + new BundleArchiveBuilder() + .symbolicName("b") + .exportPackage("b") + .build()) + .build(), + false + ); + try { + installSubsystem( + shared, + "a", + new SubsystemArchiveBuilder() + .symbolicName("a") + .type(SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION + + ';' + + AriesProvisionDependenciesDirective.INSTALL.toString()) + .bundle( + "a", + new BundleArchiveBuilder() + .symbolicName("a") + .importPackage("a") + .build()) + .build(), + true + ); + assertState(EnumSet.of(State.INSTALLED, State.RESOLVED), b); + } + catch (Exception e) { + e.printStackTrace(); + fail("Subsystem should have installed"); + } + } } Modified: aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java?rev=1727424&r1=1727423&r2=1727424&view=diff ============================================================================== --- aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java (original) +++ aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/util/SubsystemArchiveBuilder.java Thu Jan 28 19:27:49 2016 @@ -49,6 +49,10 @@ public class SubsystemArchiveBuilder { return header(Constants.EXPORT_PACKAGE, value); } + public SubsystemArchiveBuilder exportService(String value) { + return header(SubsystemConstants.SUBSYSTEM_EXPORTSERVICE, value); + } + public SubsystemArchiveBuilder file(String name, InputStream value) { bundle.add(name, value); return this; @@ -63,14 +67,22 @@ public class SubsystemArchiveBuilder { return header(Constants.IMPORT_PACKAGE, value); } - public SubsystemArchiveBuilder requireBundle(String value) { - return header(Constants.REQUIRE_BUNDLE, value); + public SubsystemArchiveBuilder importService(String value) { + return header(SubsystemConstants.SUBSYSTEM_IMPORTSERVICE, value); } public SubsystemArchiveBuilder provideCapability(String value) { return header(Constants.PROVIDE_CAPABILITY, value); } + public SubsystemArchiveBuilder requireBundle(String value) { + return header(Constants.REQUIRE_BUNDLE, value); + } + + public SubsystemArchiveBuilder requireCapability(String value) { + return header(Constants.REQUIRE_CAPABILITY, value); + } + public SubsystemArchiveBuilder subsystem(String name, InputStream value) { return file(name + ESA_EXTENSION, value); }