Skip to content

Commit fd70696

Browse files
committed
Refresh a subset of bundles if they provide missing requirements
When refreshing large sets of bundles the resolver sometimes take a choice where a requirement can't be bound even though it is there and resolvable. This adds some additional code to detect this situation and then refresh a smaller set of bundles that provide these until a max retry is reached, all bundles are resolved, or all providers have been tried to refresh already.
1 parent be9017c commit fd70696

File tree

4 files changed

+165
-4
lines changed

4 files changed

+165
-4
lines changed

bundles/org.eclipse.equinox.simpleconfigurator/META-INF/MANIFEST.MF

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-SymbolicName: org.eclipse.equinox.simpleconfigurator;singleton:=true
4-
Bundle-Version: 1.5.100.qualifier
4+
Bundle-Version: 1.5.200.qualifier
55
Bundle-Name: %bundleName
66
Bundle-Vendor: %providerName
77
Bundle-Localization: plugin
88
Bundle-Activator: org.eclipse.equinox.internal.simpleconfigurator.Activator
99
Bundle-ActivationPolicy: lazy
1010
Import-Package: org.eclipse.osgi.framework.console;version="1.0.0";resolution:=optional,
11+
org.eclipse.osgi.report.resolution;version="[1.0.0,2.0.0]",
1112
org.eclipse.osgi.service.datalocation;version="1.0.0";resolution:=optional,
1213
org.osgi.framework;version="1.3.0",
14+
org.osgi.framework.hooks.resolver;version="[1.0.0,2.0.0]",
1315
org.osgi.framework.namespace;version="1.0.0",
1416
org.osgi.framework.startlevel;version="1.0.0",
1517
org.osgi.framework.wiring;version="1.2.0",
1618
org.osgi.resource;version="1.0.0",
1719
org.osgi.service.packageadmin;version="1.2.0",
20+
org.osgi.service.resolver;version="[1.1.0,2.0.0]",
1821
org.osgi.service.startlevel;version="1.0.0",
1922
org.osgi.util.tracker;version="1.3.0"
2023
Export-Package: org.eclipse.equinox.internal.provisional.configurator;

bundles/org.eclipse.equinox.simpleconfigurator/src/org/eclipse/equinox/internal/simpleconfigurator/Activator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* Otherwise, no uninstallation will not be done.
4444
*/
4545
public class Activator implements BundleActivator {
46-
public final static boolean DEBUG = false;
46+
public final static boolean DEBUG = Boolean.getBoolean("equinox.simpleconfigurator.debug");
4747

4848
/**
4949
* If this property is set to true, simpleconfigurator will attempt to read

bundles/org.eclipse.equinox.simpleconfigurator/src/org/eclipse/equinox/internal/simpleconfigurator/ConfigApplier.java

+98-2
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@
1919
import java.net.*;
2020
import java.util.*;
2121
import java.util.concurrent.CountDownLatch;
22+
import java.util.stream.Stream;
2223
import org.eclipse.equinox.internal.simpleconfigurator.utils.*;
24+
import org.eclipse.osgi.report.resolution.ResolutionReport.Entry.Type;
2325
import org.osgi.framework.*;
26+
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
2427
import org.osgi.framework.namespace.*;
2528
import org.osgi.framework.startlevel.BundleStartLevel;
2629
import org.osgi.framework.wiring.*;
2730
import org.osgi.resource.Namespace;
2831
import org.osgi.resource.Requirement;
2932
import org.osgi.service.packageadmin.PackageAdmin;
33+
import org.osgi.service.resolver.ResolutionException;
3034

3135
class ConfigApplier {
36+
3237
private static final String LAST_BUNDLES_INFO = "last.bundles.info"; //$NON-NLS-1$
3338
private static final String PROP_DEVMODE = "osgi.dev"; //$NON-NLS-1$
3439

@@ -40,8 +45,17 @@ class ConfigApplier {
4045

4146
private final Bundle callingBundle;
4247
private final URI baseLocation;
48+
private boolean deepRefresh;
49+
private int maxRefreshTry;
4350

4451
ConfigApplier(BundleContext context, Bundle callingBundle) {
52+
deepRefresh = Boolean.parseBoolean(context.getProperty("equinox.simpleconfigurator.deeprefresh"));
53+
String maxRefreshValue = context.getProperty("equinox.simpleconfigurator.maxrefresh");
54+
if (maxRefreshValue != null) {
55+
maxRefreshTry = Integer.parseInt(maxRefreshValue);
56+
} else {
57+
maxRefreshTry = 10;
58+
}
4559
manipulatingContext = context;
4660
this.callingBundle = callingBundle;
4761
runningOnEquinox = "Eclipse".equals(context.getProperty(Constants.FRAMEWORK_VENDOR)); //$NON-NLS-1$
@@ -115,11 +129,93 @@ void install(URL url, boolean exclusiveMode) throws IOException {
115129
if (additionalRefresh.length > 0)
116130
refreshPackages(additionalRefresh, manipulatingContext);
117131
}
132+
}
133+
if (deepRefresh) {
134+
// when refreshing large sets of bundles the resolver sometimes take a choice
135+
// where a requirement can't be bound even though it is there and resolvable
136+
// this code collects all those requirements and refresh the smaller set of
137+
// bundles that provide these until a max retry is reached, all bundles are
138+
// resolved, or all providers have been tried to refresh already
139+
Set<Bundle> bundlesTried = new HashSet<>();
140+
Collection<Bundle> provider;
141+
Set<Bundle> doNotRefresh = getDoNotRefresh();
142+
int maxtry = maxRefreshTry;
143+
do {
144+
provider = getUnresolvedRequirementsProvider(doNotRefresh);
145+
if (provider.isEmpty() || !bundlesTried.addAll(provider)) {
146+
// nothing more we can do...
147+
break;
148+
}
149+
if (Activator.DEBUG) {
150+
System.out.println("There are " + provider.size() + " bundles to refresh...");
151+
for (Bundle bundle : provider) {
152+
System.out.println("\t" + bundle.getSymbolicName() + " " + bundle.getVersion());
153+
}
154+
}
155+
CountDownLatch latch = new CountDownLatch(1);
156+
frameworkWiring.refreshBundles(provider, event -> {
157+
if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
158+
latch.countDown();
159+
}
160+
});
161+
try {
162+
latch.await();
163+
} catch (InterruptedException e) {
164+
Thread.currentThread().interrupt();
165+
}
166+
} while (maxtry-- > 0);
167+
}
118168
}
169+
startBundles(toStart.toArray(new Bundle[toStart.size()]));
170+
}
119171

172+
/**
173+
* This method performs a resolve on all unresolved bundles and captures
174+
* <ol>
175+
* <li>Missing capabilities</li>
176+
* <li>Use constraint violations</li>
177+
* </ol>
178+
*
179+
* after that each unique requirement is looked up in the wiring if there is any
180+
* provider for it, if that is the case, the requirements is not really missing
181+
* or part of a conflicting chain
182+
*
183+
* @param doNotRefresh
184+
*
185+
* @return a collection of bundles that potentially can provide a missing
186+
* requirement
187+
*/
188+
private Collection<Bundle> getUnresolvedRequirementsProvider(Set<Bundle> doNotRefresh) {
189+
ResolutionReportListener reportListener = new ResolutionReportListener();
190+
ServiceRegistration<ResolverHookFactory> hookReg = manipulatingContext
191+
.registerService(ResolverHookFactory.class,
192+
reportListener, null);
193+
try {
194+
if (frameworkWiring.resolveBundles(null)) {
195+
return List.of();
196+
}
197+
if (Activator.DEBUG) {
198+
System.out.println("There are unresolved bundles...");
199+
}
200+
} finally {
201+
hookReg.unregister();
120202
}
121-
122-
startBundles(toStart.toArray(new Bundle[toStart.size()]));
203+
return reportListener.getReports().stream()
204+
.flatMap(rr -> rr.getEntries().values().stream().flatMap(Collection::stream)).flatMap(error -> {
205+
if (error.getType() == Type.MISSING_CAPABILITY) {
206+
Requirement requirement = (Requirement) error.getData();
207+
if (!"optional".equals(requirement.getDirectives().get("resolution"))) {
208+
return Stream.of(requirement);
209+
}
210+
}
211+
if (error.getType() == Type.USES_CONSTRAINT_VIOLATION) {
212+
ResolutionException ex = (ResolutionException) error.getData();
213+
return ex.getUnresolvedRequirements().stream();
214+
}
215+
return Stream.empty();
216+
}).distinct().flatMap(req -> frameworkWiring.findProviders(req).stream())
217+
.map(bc -> bc.getResource().getBundle())
218+
.distinct().filter(bundle -> !doNotRefresh.contains(bundle)).toList();
123219
}
124220

125221
private Bundle[] getAdditionalRefresh(Set<Bundle> previouslyResolved, Collection<Bundle> toRefresh) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Christoph Läubrich and others.
3+
* All rights reserved.
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which accompanies this distribution,
6+
* and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.equinox.internal.simpleconfigurator;
15+
16+
import java.util.Collection;
17+
import java.util.List;
18+
import java.util.concurrent.CopyOnWriteArrayList;
19+
import org.eclipse.osgi.report.resolution.ResolutionReport;
20+
import org.osgi.framework.hooks.resolver.ResolverHook;
21+
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
22+
import org.osgi.framework.wiring.*;
23+
24+
class ResolutionReportListener implements ResolverHookFactory, ResolverHook, ResolutionReport.Listener {
25+
26+
private List<ResolutionReport> reports = new CopyOnWriteArrayList<>();
27+
28+
@Override
29+
public ResolverHook begin(Collection<BundleRevision> triggers) {
30+
return this;
31+
}
32+
33+
@Override
34+
public void handleResolutionReport(ResolutionReport report) {
35+
reports.add(report);
36+
}
37+
38+
@Override
39+
public void filterResolvable(Collection<BundleRevision> candidates) {
40+
// nothing to do...
41+
}
42+
43+
@Override
44+
public void filterSingletonCollisions(BundleCapability singleton,
45+
Collection<BundleCapability> collisionCandidates) {
46+
// nothing to do...
47+
}
48+
49+
@Override
50+
public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
51+
// nothing to do...
52+
}
53+
54+
@Override
55+
public void end() {
56+
// nothing to do...
57+
}
58+
59+
public List<ResolutionReport> getReports() {
60+
return List.copyOf(reports);
61+
}
62+
}

0 commit comments

Comments
 (0)