Skip to content

Commit ec6b508

Browse files
committed
Improve substitution package handling and filter uses locally
Currently substitution packages handling can lead to a situation where the number of performed permutation increase considerably and the finding of a solution takes very long. In some cases it even times out and results in a failed resolve state. This now is improved by the following changes: When a permutation is removed from the stack, every requirement is checked if it is a substitution package and then resolve it to either external or internal case first: - if more candidates exits for this requirement a permutation is added to account for this alternative solution - now all other providers except the current one are dropped to make this a single provider as the choice can not be reverted and therefore other permutations in this path will be invalid - then it must be checked if the package resolves to an external provider in which case we need to drop the exported package capabilities of this bundle for the given package - after that all requirements are checked if they have only a single provider (what can happen independently or as part of the resolving to either external or internal) the use constraints are checked if they are in conflict with a currently selected provider and dropping such invalid choices - finally after that we check the package space consistency (that is a far more expensive check) what probably will result in more permutations created and checked until we found the best result to use
1 parent 2d20188 commit ec6b508

File tree

8 files changed

+285
-14
lines changed

8 files changed

+285
-14
lines changed

bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ public void testSubstitutableExports03() throws BundleException, IOException {
10931093

10941094
List<ModuleWire> providedWiresF = wiringF.getProvidedModuleWires(PackageNamespace.PACKAGE_NAMESPACE);
10951095
assertEquals("Wrong number of provided wires: " + providedWiresF, 0, providedWiresF.size());
1096-
assertSucessfulWith(report, 2, 1, 1, 1);
1096+
assertSucessfulWith(report, 3, 1, 1, 1); // TODO why this got worse? Do we need to respect resolve order?
10971097
}
10981098

10991099
@Test
@@ -3940,7 +3940,7 @@ public void testSubstitutionWithMoreThan2Providers() throws BundleException, IOE
39403940
modules.add(installDummyModule(manifest, manifest, container));
39413941
}
39423942
report = container.resolve(modules, true);
3943-
assertSucessfulWith(report, 15, 62, 47, 5);
3943+
assertSucessfulWith(report, 6, 75, 19, 3);
39443944
}
39453945

39463946
protected void assertSucessfulWith(ResolutionReport report, int maxProcessed, int maxSubstitution,
@@ -4369,14 +4369,14 @@ private static void assertWires(List<ModuleWire> required, List<ModuleWire>... p
43694369
@Test
43704370
public void testLocalUseConstraintViolations() throws Exception {
43714371
ResolutionReport result = resolveTestSet("set1");
4372-
assertSucessfulWith(result, 6, 20, 23, 6);
4372+
assertSucessfulWith(result, 1, 29, 1, 0);
43734373
}
43744374

43754375
@Test
43764376

43774377
public void testLocalUseConstraintViolations2() throws Exception {
43784378
ResolutionReport result = resolveTestSet("set2");
4379-
assertSucessfulWith(result, 3, 3, 1, 3);
4379+
assertSucessfulWith(result, 3, 6, 1, 0);
43804380
}
43814381

43824382
@Test
@@ -4388,13 +4388,13 @@ public void testSubstitutionPackageResolution() throws Exception {
43884388
// - now util has to be removed as a provider only having libg as the only one left
43894389
// - bndlib now can only use libg for exceptions package but this conflicts with result from util that has use constraint on exceptions package
43904390
// - on second iteration now libg chose external and drops it exports removing it from util+bndlib -> resolved state
4391-
assertSucessfulWith(result, 1, 2, 1, 0);
4391+
assertSucessfulWith(result, 2, 2, 1, 0); // TODO why became this worse?
43924392
}
43934393

43944394
@Test
43954395
public void testLargeSet() throws Exception {
43964396
ResolutionReport result = resolveModuleDatabaseDump("big", TimeUnit.MINUTES.toSeconds(5));
4397-
assertSucessfulWith(result, 2661, 29, 31623, 9272);
4397+
assertSucessfulWith(result, 117, 344, 1, 172);
43984398
}
43994399

44004400
private ResolutionReport resolveModuleDatabaseDump(String testSetName, long batchTimeoutSeconds) throws Exception {

bundles/org.eclipse.osgi.tests/test_files/containerTests/sub.f.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ Bundle-ManifestVersion: 2
22
Bundle-SymbolicName: sub.f
33
Bundle-Version: 1.0.0
44
Export-Package: bar; version="1.0.1"
5-
Import-Package: bar; foo
5+
Import-Package: bar, foo
66

bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Backlog.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.felix.resolver;
2020

2121
import java.util.LinkedHashMap;
22+
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Map.Entry;
2425
import org.apache.felix.resolver.Candidates.FaultyResourcesReport;
@@ -56,7 +57,8 @@ public Backlog(ResolveSession session) {
5657
public Candidates getNext() {
5758
Candidates candidates;
5859
while ((candidates = session.getNextPermutation()) != null) {
59-
ResolutionError substituteError = candidates.checkSubstitutes();
60+
List<Candidates> alternatives = candidates.process(session.getLogger());
61+
alternatives.forEach(alt -> session.addPermutation(PermutationType.SUBSTITUTE, alt));
6062
FaultyResourcesReport report = candidates.getFaultyResources();
6163
if (!report.isMissing() || session.isCancelled()) {
6264
return candidates;

bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Candidates.java

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Map;
3131
import java.util.Map.Entry;
32+
import java.util.Objects;
3233
import java.util.Set;
3334
import java.util.TreeMap;
3435
import java.util.concurrent.atomic.AtomicBoolean;
@@ -56,6 +57,12 @@
5657

5758
class Candidates
5859
{
60+
61+
// Emergency Switch to disable the usage of the new {@link SubstitutionPackages}
62+
// computation JVM wide. Will be removed when we are confident that no
63+
// regressions occurs.
64+
private static final boolean USE_LEGACY_SUBSTITUTION_PACKAGES = Boolean
65+
.getBoolean("felix.resolver.substitution.legacy");
5966
private static final boolean FILTER_USES = Boolean
6067
.parseBoolean(System.getProperty("felix.resolver.candidates.filteruses", "true"));
6168
static class PopulateResult {
@@ -728,6 +735,77 @@ public List<Capability> getCandidates(Requirement req)
728735
return null;
729736
}
730737

738+
public boolean isSubstitutionPackage(Requirement req) {
739+
CandidateSelector candidates = m_candidateMap.get(req);
740+
if (candidates == null) {
741+
return false;
742+
}
743+
return candidates.isSubstitutionPackage();
744+
}
745+
746+
/**
747+
* In case of substitution packages we need to be able to discard a capability
748+
* from all candidate lists if we choose to use the import over the export.
749+
*
750+
* @param requirement the requirement for what the export package capability
751+
* must be dropped.
752+
*/
753+
public void discardExportPackageCapability(Requirement requirement) {
754+
CandidateSelector candidates = m_candidateMap.get(requirement);
755+
if (candidates == null) {
756+
return;
757+
}
758+
List<Capability> packages = candidates.getSubstitutionPackages();
759+
for (Capability capability : packages) {
760+
Collection<Requirement> dependents = m_dependentMap.get(capability);
761+
if (dependents == null || dependents.isEmpty()) {
762+
continue;
763+
}
764+
for (Requirement dependent : dependents) {
765+
CandidateSelector dependentCandidates = m_candidateMap.get(dependent);
766+
Capability current = dependentCandidates.getCurrentCandidate();
767+
if (current == capability) {
768+
removeFirstCandidate(dependent);
769+
} else {
770+
List<Capability> remaining = new ArrayList<Capability>(
771+
dependentCandidates.getRemainingCandidates());
772+
if (remaining.remove(capability)) {
773+
CopyOnWriteSet<Capability> capPath = m_delta.getOrCompute(dependent);
774+
capPath.add(capability);
775+
if (remaining.isEmpty()) {
776+
m_candidateMap.remove(requirement);
777+
} else {
778+
m_candidateMap.put(requirement, dependentCandidates.copyWith(remaining));
779+
}
780+
}
781+
}
782+
}
783+
}
784+
}
785+
786+
/**
787+
* In case of substitution packages we need to take a decision if the internal
788+
* or external binding is used. If the internal one is chosen, then no other
789+
* providers are to be selected for this package and we can remove them to
790+
* prevent further permutation.
791+
* @param req the requirement to clear from other providers
792+
* @return
793+
*/
794+
795+
public CandidateSelector dropOtherCandidates(Requirement req) {
796+
// this is a special case where we need to completely replace the
797+
// CandidateSelector
798+
// this method should never be called from normal Candidates permutations
799+
CandidateSelector candidates = m_candidateMap.get(req);
800+
Capability currentCandidate = candidates.getCurrentCandidate();
801+
if (currentCandidate == null || candidates.getRemainingCandidates().size() == 1) {
802+
return candidates;
803+
}
804+
candidates = candidates.copyWith(Collections.singletonList(currentCandidate));
805+
m_candidateMap.put(req, candidates);
806+
return candidates;
807+
}
808+
731809
public Capability getFirstCandidate(Requirement req)
732810
{
733811
CandidateSelector candidates = m_candidateMap.get(req);
@@ -983,7 +1061,7 @@ public ResolutionError prepare()
9831061

9841062
m_candidateMap.trim();
9851063
m_dependentMap.trim();
986-
1064+
m_candidateMap.forEach((r, c) -> c.calculate(r));
9871065
// mark the selectors as unmodifiable now
9881066
m_candidateSelectorsUnmodifiable.set(true);
9891067
return null;
@@ -1182,8 +1260,20 @@ public Candidates copy()
11821260
m_delta.deepClone());
11831261
}
11841262

1185-
public Candidates permutate(Requirement req)
1263+
public Candidates permutate(Requirement req) {
1264+
return permutate(req, false);
1265+
}
1266+
1267+
public Candidates permutate(Requirement req, boolean always)
11861268
{
1269+
if (always) {
1270+
Candidates perm = copy();
1271+
perm.removeFirstCandidate(req);
1272+
if (perm.getFirstCandidate(req) == null) {
1273+
return null;
1274+
}
1275+
return perm;
1276+
}
11871277
if (!Util.isMultiple(req) && canRemoveCandidate(req))
11881278
{
11891279
Candidates perm = copy();
@@ -1196,6 +1286,65 @@ public Candidates permutate(Requirement req)
11961286
return null;
11971287
}
11981288

1289+
public List<Candidates> process(Logger logger) {
1290+
if (Candidates.USE_LEGACY_SUBSTITUTION_PACKAGES) {
1291+
checkSubstitutes();
1292+
return Collections.emptyList();
1293+
}
1294+
List<Candidates> alternatives = new ArrayList<>();
1295+
// first resolve all substitution packages, this can not run in parallel as we
1296+
// maybe drop providers from other requirements as well
1297+
m_candidateMap.keySet().stream().filter(Objects::nonNull).forEach(requirement -> {
1298+
Candidates candidates = resolveSubstitutionPackages(requirement, logger);
1299+
if (candidates != null) {
1300+
alternatives.add(candidates);
1301+
}
1302+
});
1303+
// now reduce providers list based on uses ... this can possibly be run in
1304+
// parallel as we only operate on a single requirement
1305+
m_candidateMap.keySet().stream().filter(Objects::nonNull).forEach(requirement -> {
1306+
ProblemReduction.removeInvalidPackageProvider(this, requirement, logger);
1307+
});
1308+
return alternatives;
1309+
}
1310+
1311+
/**
1312+
* Resolves all substitution packages for the given candidate set and returns a
1313+
* list of new permutations that has to be considered, see <a href=
1314+
* "https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#framework.module-import.export.same.package">the
1315+
* spec</a> for reference. The rules are as follows for a package that is
1316+
* substitutable:
1317+
* <ol>
1318+
* <li><code>External</code> - If this resolves to an export statement in
1319+
* <b>another bundle</b>, then the overlapping export definition <b>in this
1320+
* bundle</b> is discarded.</li>
1321+
* <li><code>Internal</code> - If it is resolved to an export statement in
1322+
* <b>this bundle</b>, then the overlapping import definition <b>in this
1323+
* bundle</b> is discarded.</li>
1324+
* <li><code>Unresolved</code> - There is no matching export definition. In this
1325+
* case the framework is free to discard either the overlapping export
1326+
* definition or overlapping import definition in this bundle. If the export
1327+
* definition is discarded and the import definition is not optional then the
1328+
* bundle will fail to resolve.</li>
1329+
* </ol>
1330+
*
1331+
* @return the {@link Candidates} permutation for an alternative substitution
1332+
* choice or <code>null</code> otherwise
1333+
*/
1334+
private Candidates resolveSubstitutionPackages(Requirement requirement, Logger logger) {
1335+
if (!isSubstitutionPackage(requirement)) {
1336+
return null;
1337+
}
1338+
Candidates permutation = permutate(requirement, true);
1339+
dropOtherCandidates(requirement);
1340+
Capability firstCandidate = getFirstCandidate(requirement);
1341+
if (firstCandidate == null || !firstCandidate.getResource().equals(requirement.getResource())) {
1342+
// Mapped to the external case, we need to discard our own export capability
1343+
discardExportPackageCapability(requirement);
1344+
}
1345+
return permutation;
1346+
}
1347+
11991348
public boolean canRemoveCandidate(Requirement req)
12001349
{
12011350
CandidateSelector candidates = m_candidateMap.get(req);

bundles/org.eclipse.osgi/felix/src/org/apache/felix/resolver/Logger.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.felix.resolver;
2020

21+
import java.util.Collection;
2122
import java.util.List;
2223
import java.util.function.Function;
2324
import org.osgi.resource.Capability;
@@ -201,4 +202,19 @@ public void logProcessPermutation(PermutationType type)
201202
public void logPermutationProcessed(ResolutionError error) {
202203

203204
}
205+
206+
void logCandidates(ResolveSession session, Candidates candidates) {
207+
Collection<Resource> mandatoryResources = session.getMandatoryResources();
208+
Collection<Resource> optionalResources = session.getOptionalResources();
209+
for (Resource resource : mandatoryResources) {
210+
logCandidates(resource, candidates);
211+
}
212+
for (Resource resource : optionalResources) {
213+
logCandidates(resource, candidates);
214+
}
215+
}
216+
217+
private void logCandidates(Resource resource, Candidates candidates) {
218+
logCandidates(resource, req -> candidates.getCandidates(req));
219+
}
204220
}

0 commit comments

Comments
 (0)