-
Notifications
You must be signed in to change notification settings - Fork 408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refresh the bundles after uninstalling #1253
Conversation
Signed-off-by: Sheng Chen <[email protected]>
e600cb5
to
dcaa1a0
Compare
Tests fail on Jenkins |
if (installedBundle != null) { | ||
Module module = installedBundle.adapt(Module.class); | ||
if (isSingleton(module.getCurrentRevision())) { | ||
// We have installed this singleton bundle before, so skip this one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Support installing two different versions of a non-singleton jar, make sense to me. But if two extensions try to install the same version of a non-singleton jar, will it succeed?
frameworkWiring.refreshBundles(toRefresh, new FrameworkListener() { | ||
@Override | ||
public void frameworkEvent(FrameworkEvent event) { | ||
latch.countDown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need check the specific finish event such as PACKAGES_REFRESHED, ERROR, STOPPED ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I should add event.getType() == FrameworkEvent.PACKAGES_REFRESHED
check
Might caused by the JUnit 5's required bundles are not installed in the test environment. I'm trying the build a new bundle for test purpose which has no required bundles. |
Signed-off-by: Sheng Chen <[email protected]>
Signed-off-by: Sheng Chen <[email protected]>
@@ -194,7 +215,9 @@ private static void refreshBundles(Set<Bundle> toRefresh, FrameworkWiring framew | |||
frameworkWiring.refreshBundles(toRefresh, new FrameworkListener() { | |||
@Override | |||
public void frameworkEvent(FrameworkEvent event) { | |||
latch.countDown(); | |||
if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when it's FrameworkEvent.ERROR, also exit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to find if there is any example also handling other events when refresh the packages but with no success.
Is there any example I can refer to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No example yet. i just read the API document of FrameworkListener, it says when there is any exception happening during refresh, it will report ERROR event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Then how about log an error message when this happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we always are able to receive a FrameworkEvent.PACKAGES_REFRESHED event regardless of success or fail, i'm OK. My point is to make sure the latch.await is not blocking when there is bad status happening.
boolean shouldSkip = false; | ||
for (Bundle bundle : installedBundleList) { | ||
Module module = bundle.adapt(Module.class); | ||
if (isSingleton(module.getCurrentRevision())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The singleton information can be resolved from the method getBundleInfo(String bundleLocation)
directly, no need to get it from the module revision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean getting it by parsing the symbol name? What if it contains whitespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a sample format Bundle-SymbolicName: org.eclipse.jdt.ls.core;singleton:=true
in MANIFEST.MF. Currently before installing the new bundle, we already have logic to resolve the symbolic name and version info from the manifest file directly, why not bring the singleton attribute by the way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a way to do it:
ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName);
if (symbolicNameElements.length > 0) {
symbolicName = symbolicNameElements[0].getValue();
String singleton = symbolicNameElements[0].getDirective(Constants.SINGLETON_DIRECTIVE);
isSingleton = "true".equals(singleton);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the existing utility method in BundleUtils. Looks like your sample code is better.
private static BundleInfo getBundleInfo(String bundleLocation) throws IOException {
try (JarFile jarFile = new JarFile(bundleLocation)) {
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
Attributes mainAttributes = manifest.getMainAttributes();
if (mainAttributes != null) {
String bundleVersion = mainAttributes.getValue(Constants.BUNDLE_VERSION);
if (StringUtils.isBlank(bundleVersion)) {
return null;
}
String symbolicName = mainAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
if (StringUtils.isNotBlank(symbolicName) && symbolicName.indexOf(';') >= 0) {
symbolicName = symbolicName.substring(0, symbolicName.indexOf(';'));
}
return new BundleInfo(bundleVersion, symbolicName);
}
}
}
return null;
}
Signed-off-by: Sheng Chen <[email protected]>
Code updated. |
Signed-off-by: Sheng Chen <[email protected]>
4645b18
to
a34f88a
Compare
latch.countDown(); | ||
} else if (event.getType() == FrameworkEvent.ERROR) { | ||
latch.countDown(); | ||
JavaLanguageServerPlugin.logError("Error happens when refreshing the bundles"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
carry the throwable info into the log.
Signed-off-by: Sheng Chen <[email protected]>
test this please |
assertEquals(newBundle.getState(), Bundle.ACTIVE); | ||
} finally { | ||
// Uninstall the bundle to clean up the testing bundle context. | ||
newBundle.uninstall(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BundleUtils.loadBundles(Arrays.asList(oldBundlePath, oldDependencyPath));
BundleUtils.loadBundles(Arrays.asList(newBundlePath, newDependencyPath));
probably throw uncaught exception, need extend the enclosing scope for try->finally.
Signed-off-by: Sheng Chen <[email protected]>
@fbricon @testforstephen @snjeza Please let me know if there is any concern about this change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me what happens if 2 extensions are loaded, each of them having a dependency on the same bundle, same version, singleton or not, coming from 2 different urls
@@ -158,6 +160,187 @@ public void testLoadSameBundleMultipleTimes() throws Exception { | |||
} | |||
} | |||
|
|||
@Test | |||
public void testLoadMultipleNonSongletonBundles() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testLoadMultipleNonSingletonBundles
if (bundleInfo == null) { | ||
status.add(new Status(IStatus.ERROR, context.getBundle().getSymbolicName(), "Failed to get bundleInfo for bundle from " + bundleLocation, null)); | ||
continue; | ||
} | ||
|
||
// Platform.getBundle() won't return the bundle with INSTALLED state, so we use this Map to persist the current installed bundles |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can try the following method:
private static Bundle getBundle(String symbolicName) {
FrameworkWiring fwkWiring = JavaLanguageServerPlugin.getBundleContext().getBundle(0).adapt(FrameworkWiring.class);
BundleContext context = fwkWiring.getBundle().getBundleContext();
if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName)) {
symbolicName = context.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).getSymbolicName();
}
StringBuilder filter = new StringBuilder();
filter.append('(')
.append(IdentityNamespace.IDENTITY_NAMESPACE)
.append('=')
.append(symbolicName)
.append(')');
Map<String, String> directives = Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
Collection<BundleCapability> matchingBundleCapabilities = fwkWiring.findProviders(ModuleContainer.createRequirement(IdentityNamespace.IDENTITY_NAMESPACE, directives, Collections.emptyMap()));
if (matchingBundleCapabilities.isEmpty()) {
return null;
}
Bundle[] results = matchingBundleCapabilities.stream().map(c -> c.getRevision().getBundle())
// Remove all the bundles that are uninstalled
.filter(bundle -> (bundle.getState() & (Bundle.UNINSTALLED)) == 0)
.sorted((b1, b2) -> b2.getVersion().compareTo(b1.getVersion())) // highest version first
.toArray(Bundle[]::new);
return results.length > 0 ? results[0] : null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @snjeza, this method works!
Signed-off-by: Sheng Chen <[email protected]>
a82445b
to
34d5f95
Compare
test this please |
Summary of the current behavior:
|
if (bundles != null) { | ||
boolean shouldSkip = false; | ||
for (Bundle bundle : bundles) { | ||
if (bundle.getVersion().equals(Version.parseVersion(bundleInfo.getVersion()))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please compute Version.parseVersion(bundleInfo.getVersion())) only once, before the loop
Signed-off-by: Sheng Chen <[email protected]>
@@ -118,11 +118,12 @@ public static void loadBundles(Collection<String> bundleLocations) throws CoreEx | |||
continue; | |||
} | |||
|
|||
Version currentBundleVersion = Version.parseVersion(bundleInfo.getVersion()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move within if (bundles != null)
block please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Signed-off-by: Sheng Chen <[email protected]>
This PR add two things in
loadBundles
:Why need this change
Scenario one
When a JDT.LS extension get updated, though the required bundle's version might be the same, but the location might be changed. Then the previous bundle get uninstalled, and the new bundle get installed. Now the framework will have both the uninstalled and installed bundles. When the bundle is declared as singleton, we will get an error when start the installed bundle.
Trigger the refresh action can avoid this exception
Scenario two
Two different extensions both want to install the same bundle with different version. If the bundle is declared as singleton, installing two different version of this bundle will cause exceptions.
So we can use a Collection to persist those installed singleton bundles.
Signed-off-by: Sheng Chen [email protected]