diff --git a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java index 835064645a..d526620998 100644 --- a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java +++ b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java @@ -386,6 +386,14 @@ private synchronized List initializeContentPath() throws Exception { return m_contentPath; } + // A closed revision has no content, but a class loader of its + // wiring may still look up resources concurrently. Don't cache + // the empty path, since a refresh can reopen this revision with + // resetContent(). + if (m_content == null) + { + return Collections.emptyList(); + } List contentList = new ArrayList<>(); calculateContentPath(this, getContent(), contentList, true); diff --git a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java index 2b7feb85fe..f8be8a8aa0 100644 --- a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java +++ b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java @@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; @@ -29,9 +30,12 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; @@ -43,8 +47,11 @@ import org.junit.jupiter.api.Test; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkEvent; import org.osgi.framework.launch.Framework; +import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; class ResourceLoadingTest { @@ -227,6 +234,53 @@ void resourceLoadingUsingURLClassLoaderJDK9() throws Exception { assertThat(urlClassLoader.getResource("ej2.txt")).isNotNull(); } + @Test + void resourceLoadingFromClosedRevisionFindsNothing() throws Exception + { + String bmf = "Bundle-SymbolicName: cap.bundle\n" + + "Bundle-Version: 1.2.3.Blah\n" + + "Bundle-ManifestVersion: 2\n" + + "Import-Package: org.osgi.framework\n"; + File bundleFile = File.createTempFile("felix-bundle", ".jar", tempDir); + + Manifest mf = new Manifest(new ByteArrayInputStream(bmf.getBytes("utf-8"))); + mf.getMainAttributes().putValue("Manifest-Version", "1.0"); + JarOutputStream os = new JarOutputStream(new FileOutputStream(bundleFile), mf); + + String name = "b1.txt"; + os.putNextEntry(new ZipEntry(name)); + os.write("This is a Test".getBytes()); + os.close(); + + Bundle testBundle = felix.getBundleContext().installBundle(bundleFile.toURI().toASCIIString()); + testBundle.start(); + + BundleRevisionImpl oldRevision = (BundleRevisionImpl) testBundle.adapt(BundleRevision.class); + BundleWiringImpl.BundleClassLoader oldClassLoader = + (BundleWiringImpl.BundleClassLoader) testBundle.adapt(BundleWiring.class).getClassLoader(); + assertThat(oldClassLoader.findResource(name)).isNotNull(); + + // Updating and refreshing closes the old revision, while a lookup + // through the class loader of its wiring may still be in progress + try (FileInputStream updated = new FileInputStream(bundleFile)) + { + testBundle.update(updated); + } + CountDownLatch refreshed = new CountDownLatch(1); + felix.adapt(FrameworkWiring.class).refreshBundles(Collections.singletonList(testBundle), event -> + { + if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) + { + refreshed.countDown(); + } + }); + assertThat(refreshed.await(10, TimeUnit.SECONDS)).as("refresh completed").isTrue(); + assertThat(oldRevision.getContent()).as("precondition: old revision is closed").isNull(); + + assertThat(oldClassLoader.findResource(name)).isNull(); + assertThat(oldClassLoader.findResources(name).hasMoreElements()).isFalse(); + } + ClassLoader createClassLoader(Bundle bundle) { List urls = new ArrayList<>(); Collection resources = bundle.adapt(BundleWiring.class).listResources("/", "*.jar", BundleWiring.LISTRESOURCES_LOCAL);