felix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pa...@apache.org
Subject svn commit: r1805119 - in /felix/trunk/framework/src: main/java/org/apache/felix/framework/BundleWiringImpl.java test/java/org/apache/felix/framework/BundleWiringImplTest.java
Date Tue, 15 Aug 2017 22:33:05 GMT
Author: pauls
Date: Tue Aug 15 22:33:05 2017
New Revision: 1805119

URL: http://svn.apache.org/viewvc?rev=1805119&view=rev
Log:
FELIX-5665: Delegate class loads for sun.reflect.Generated* classes correctly and cache the
result (both, hit and miss) in order to speed up the class lookup. Solution based on a patch
provided by Anil Attuluri - Thanks! (This closes #116)

Modified:
    felix/trunk/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
    felix/trunk/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java

Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java?rev=1805119&r1=1805118&r2=1805119&view=diff
==============================================================================
--- felix/trunk/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java (original)
+++ felix/trunk/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java Tue
Aug 15 22:33:05 2017
@@ -18,29 +18,6 @@
  */
 package org.apache.felix.framework;
 
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.security.SecureClassLoader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.cache.JarContent;
 import org.apache.felix.framework.capabilityset.SimpleFilter;
@@ -76,6 +53,30 @@ import org.osgi.resource.Requirement;
 import org.osgi.resource.Wire;
 import org.osgi.service.resolver.ResolutionException;
 
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
 public class BundleWiringImpl implements BundleWiring
 {
     public final static int LISTRESOURCES_DEBUG = 1048576;
@@ -83,6 +84,15 @@ public class BundleWiringImpl implements
     public final static int EAGER_ACTIVATION = 0;
     public final static int LAZY_ACTIVATION = 1;
 
+    public static final ClassLoader CNFE_CLASS_LOADER = new ClassLoader()
+    {
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+        {
+            throw new ClassNotFoundException("Unable to load class '" + name + "'");
+        }
+    };
+
     private final Logger m_logger;
     private final Map m_configMap;
     private final StatefulResolver m_resolver;
@@ -151,6 +161,8 @@ public class BundleWiringImpl implements
     // Flag indicating whether this wiring has been disposed.
     private volatile boolean m_isDisposed = false;
 
+    private volatile ConcurrentHashMap<String, ClassLoader> m_accessorLookupCache;
+
     BundleWiringImpl(
             Logger logger, Map configMap, StatefulResolver resolver,
             BundleRevisionImpl revision, List<BundleRevision> fragments,
@@ -484,6 +496,7 @@ public class BundleWiringImpl implements
         }
         m_classLoader = null;
         m_isDisposed = true;
+        m_accessorLookupCache = null;
     }
 
     // TODO: OSGi R4.3 - This really shouldn't be public, but it is needed by the
@@ -1466,6 +1479,22 @@ public class BundleWiringImpl implements
                         ? Util.getClassPackage(name)
                                 : Util.getResourcePackage(name);
 
+                        boolean accessor = name.startsWith("sun.reflect.Generated");
+
+                        if (accessor)
+                        {
+                            if (m_accessorLookupCache == null)
+                            {
+                                m_accessorLookupCache = new ConcurrentHashMap<String,
ClassLoader>();
+                            }
+
+                            ClassLoader loader = m_accessorLookupCache.get(name);
+                            if (loader != null)
+                            {
+                                return loader.loadClass(name);
+                            }
+                        }
+
                         // Delegate any packages listed in the boot delegation
                         // property to the parent class loader.
                         if (shouldBootDelegate(pkgName))
@@ -1481,6 +1510,10 @@ public class BundleWiringImpl implements
                                         // search; otherwise, continue to look locally if
not found.
                                         if (pkgName.startsWith("java.") || (result != null))
                                         {
+                                            if (accessor)
+                                            {
+                                                m_accessorLookupCache.put(name, bdcl);
+                                            }
                                             return result;
                                         }
                             }
@@ -1495,6 +1528,33 @@ public class BundleWiringImpl implements
                             }
                         }
 
+                        if (accessor)
+                        {
+                            List<Collection<BundleRevision>> allRevisions = new
ArrayList<Collection<BundleRevision>>( 1 + m_requiredPkgs.size());
+                            allRevisions.add(m_importedPkgs.values());
+                            allRevisions.addAll(m_requiredPkgs.values());
+
+                            for (Collection<BundleRevision> revisions : allRevisions)
+                            {
+                                for (BundleRevision revision : revisions)
+                                {
+                                    ClassLoader loader = revision.getWiring().getClassLoader();
+                                    if (loader != null && loader instanceof BundleClassLoader)
+                                    {
+                                        BundleClassLoader bundleClassLoader = (BundleClassLoader)
loader;
+                                        result = bundleClassLoader.findLoadedClassInternal(name);
+                                        if (result != null)
+                                        {
+                                            m_accessorLookupCache.put(name, bundleClassLoader);
+                                            return result;
+                                        }
+                                    }
+                                }
+                            }
+                            m_accessorLookupCache.put(name, CNFE_CLASS_LOADER);
+                            CNFE_CLASS_LOADER.loadClass(name);
+                        }
+
                         // Look in the revision's imports. Note that the search may
                         // be aborted if this method throws an exception, otherwise
                         // it continues if a null is returned.
@@ -2591,6 +2651,11 @@ public class BundleWiringImpl implements
         {
             return m_wiring.toString();
         }
+
+        Class<?> findLoadedClassInternal(String name)
+        {
+            return super.findLoadedClass(name);
+        }
     }
 
     static URL convertToLocalUrl(URL url)

Modified: felix/trunk/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java?rev=1805119&r1=1805118&r2=1805119&view=diff
==============================================================================
--- felix/trunk/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
(original)
+++ felix/trunk/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
Tue Aug 15 22:33:05 2017
@@ -18,28 +18,12 @@
  */
 package org.apache.felix.framework;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.felix.framework.BundleWiringImpl.BundleClassLoader;
 import org.apache.felix.framework.cache.Content;
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.Opcodes;
@@ -52,6 +36,31 @@ import org.osgi.framework.hooks.weaving.
 import org.osgi.framework.hooks.weaving.WovenClassListener;
 import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class BundleWiringImplTest
 {
@@ -368,6 +377,283 @@ public class BundleWiringImplTest
                 dummyWovenClassListener.stateList.get(1));
     }
 
+    private ConcurrentHashMap<String, ClassLoader> getAccessorCache(BundleWiringImpl
wiring) throws NoSuchFieldException, IllegalAccessException {
+        Field m_accessorLookupCache = BundleWiringImpl.class.getDeclaredField("m_accessorLookupCache");
+        m_accessorLookupCache.setAccessible(true);
+        return (ConcurrentHashMap<String, ClassLoader>) m_accessorLookupCache.get(wiring);
+    }
+
+    @Test
+    public void testFirstGeneratedAccessorSkipClassloading() throws Exception
+    {
+
+        String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
+
+        Felix mockFramework = mock(Felix.class);
+        when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+
+        initializeSimpleBundleWiring();
+
+        when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+
+        BundleClassLoader bundleClassLoader = createBundleClassLoader(
+                BundleClassLoader.class, bundleWiring);
+        assertNotNull(bundleClassLoader);
+
+        try {
+            bundleClassLoader.loadClass(classToBeLoaded, true);
+            fail();
+        } catch (ClassNotFoundException cnf) {
+            //this is expected
+
+            //make sure boot delegation was done before CNF was thrown
+            verify(mockFramework).getBootPackages();
+
+            //make sure the class is added to the skip class cache
+            assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), BundleWiringImpl.CNFE_CLASS_LOADER);
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void initializeBundleWiringWithImportsAndRequired(Map<String, BundleRevision>
importedPkgs, Map<String, List<BundleRevision>> requiredPkgs) throws Exception
+    {
+
+        mockResolver = mock(StatefulResolver.class);
+        mockRevisionImpl = mock(BundleRevisionImpl.class);
+        mockBundle = mock(BundleImpl.class);
+
+        Logger logger = new Logger();
+        Map configMap = new HashMap();
+        List<BundleRevision> fragments = new ArrayList<BundleRevision>();
+        List<BundleWire> wires = new ArrayList<BundleWire>();
+
+        when(mockRevisionImpl.getBundle()).thenReturn(mockBundle);
+        when(mockBundle.getBundleId()).thenReturn(Long.valueOf(1));
+
+        bundleWiring = new BundleWiringImpl(logger, configMap, mockResolver,
+                mockRevisionImpl, fragments, wires, importedPkgs, requiredPkgs);
+    }
+
+    @Test
+    public void testAccessorFirstLoadFailed() throws Exception
+    {
+
+        String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
+
+        Felix mockFramework = mock(Felix.class);
+        when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+
+        Map<String, BundleRevision> importedPkgs = mock(Map.class);
+        Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
+
+        initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
+
+        when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+
+        BundleClassLoader bundleClassLoader = createBundleClassLoader(
+                BundleClassLoader.class, bundleWiring);
+        assertNotNull(bundleClassLoader);
+
+        try {
+            bundleClassLoader.loadClass(classToBeLoaded, true);
+            fail();
+        } catch (ClassNotFoundException cnf) {
+            //this is expected
+
+            //make sure boot delegation was done before CNF was thrown
+            verify(mockFramework).getBootPackages();
+
+            //make sure imported and required pkgs are searched
+            verify(importedPkgs).values();
+            verify(requiredPkgs).values();
+
+            //make sure the class is added to the skip class cache
+            assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), BundleWiringImpl.CNFE_CLASS_LOADER);
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    @Test
+    public void testAccessorSubsequentLoadFailed() throws Exception
+    {
+
+        String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
+
+        Felix mockFramework = mock(Felix.class);
+        when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+
+        Map<String, BundleRevision> importedPkgs = mock(Map.class);
+        Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
+
+        initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
+
+        when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+
+        BundleClassLoader bundleClassLoader = createBundleClassLoader(
+                BundleClassLoader.class, bundleWiring);
+        assertNotNull(bundleClassLoader);
+
+        //first attempt to populate the cache
+        try {
+            bundleClassLoader.loadClass(classToBeLoaded, true);
+            fail();
+        } catch (ClassNotFoundException cnf) {
+            //this is expected
+        }
+
+        //now test that the subsequent class load throws CNF with out boot delegation and
import/required packages
+        try {
+
+            importedPkgs = mock(Map.class);
+            requiredPkgs = mock(Map.class);
+            initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
+            mockFramework = mock(Felix.class);
+            when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+            when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+            bundleClassLoader.loadClass(classToBeLoaded, true);
+            fail();
+        } catch (ClassNotFoundException cnf) {
+            //this is expected
+
+            //make sure boot delegation was not used
+            verify(mockFramework, never()).getBootPackages();
+
+            //make sure boot import and required packages were not searched
+            verify(importedPkgs, never()).values();
+            verify(requiredPkgs, never()).values();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    private BundleRevision getBundleRevision(String classToBeLoaded, BundleClassLoader pkgBundleClassLoader,
Object value) throws ClassNotFoundException {
+        BundleRevision bundleRevision = mock(BundleRevision.class);
+        BundleWiring pkgBundleWiring = mock(BundleWiring.class);
+        when(pkgBundleClassLoader.findLoadedClassInternal(classToBeLoaded)).thenAnswer(createAnswer(value));
+        when(pkgBundleClassLoader.loadClass(classToBeLoaded)).thenAnswer(createAnswer(value));
+
+        when(pkgBundleWiring.getClassLoader()).thenReturn(pkgBundleClassLoader);
+        when(bundleRevision.getWiring()).thenReturn(pkgBundleWiring);
+        return bundleRevision;
+    }
+
+    @Test
+    public void testAccessorLoadImportPackage() throws Exception
+    {
+
+        String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
+
+        Felix mockFramework = mock(Felix.class);
+        when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+
+        Map<String, BundleRevision> importedPkgs = mock(Map.class);
+        BundleClassLoader foundClassLoader = mock(BundleClassLoader.class);
+        BundleClassLoader notFoundClassLoader = mock(BundleClassLoader.class);
+        BundleRevision bundleRevision1 = getBundleRevision(classToBeLoaded, foundClassLoader,
String.class);
+        BundleRevision bundleRevision2 = getBundleRevision(classToBeLoaded, notFoundClassLoader,
null);
+        Map<String, BundleRevision> importedPkgsActual = new HashMap<String, BundleRevision>();
+        importedPkgsActual.put("sun.reflect1", bundleRevision1);
+        importedPkgsActual.put("sun.reflect2", bundleRevision2);
+        when(importedPkgs.values()).thenReturn(importedPkgsActual.values());
+        Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
+
+        initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
+
+        when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+
+        BundleClassLoader bundleClassLoader = createBundleClassLoader(
+                BundleClassLoader.class, bundleWiring);
+        assertNotNull(bundleClassLoader);
+
+        //call class load to populate the cache
+        try {
+            Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
+            assertNotNull(result);
+            assertTrue(getAccessorCache(bundleWiring).containsKey(classToBeLoaded));
+            assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), foundClassLoader);
+            verify(foundClassLoader, times(1)).findLoadedClassInternal(classToBeLoaded);
+            verify(notFoundClassLoader, never()).findLoadedClassInternal(classToBeLoaded);
+        } catch (Exception e) {
+            fail();
+        }
+
+        //now make sure subsequent class load happens from cached revision
+        Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
+        assertNotNull(result);
+        //makes sure the look up cache is accessed and the class is loaded from cached revision
+        verify(foundClassLoader, times(1)).findLoadedClassInternal(classToBeLoaded);
+        verify(foundClassLoader, times(1)).loadClass(classToBeLoaded);
+        verify(notFoundClassLoader, never()).findLoadedClassInternal(classToBeLoaded);
+    }
+
+    private static <T> Answer<T> createAnswer(final T value) {
+        Answer<T> dummy = new Answer<T>() {
+            @Override
+            public T answer(InvocationOnMock invocation) throws Throwable {
+                return value;
+            }
+        };
+        return dummy;
+    }
+
+    @Test
+    public void testAccessorBootDelegate() throws Exception
+    {
+
+        String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
+
+        Felix mockFramework = mock(Felix.class);
+        when(mockFramework.getBootPackages()).thenReturn(new String[0]);
+
+        Map<String, BundleRevision> importedPkgs = mock(Map.class);
+        BundleRevision bundleRevision1 = mock(BundleRevision.class);
+        Map<String, BundleRevision> importedPkgsActual = new HashMap<String, BundleRevision>();
+        importedPkgsActual.put("sun.reflect1", bundleRevision1);
+        when(importedPkgs.values()).thenReturn(importedPkgsActual.values());
+        Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
+
+        ClassLoader bootDelegateClassLoader = mock(ClassLoader.class);
+
+        when(bootDelegateClassLoader.loadClass(classToBeLoaded)).thenAnswer(createAnswer(String.class));
+
+        initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
+
+        when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
+
+        Field field = bundleWiring.getClass().getDeclaredField("m_bootClassLoader");
+        field.setAccessible(true);
+        field.set(bundleWiring, bootDelegateClassLoader);
+
+        BundleClassLoader bundleClassLoader = createBundleClassLoader(
+                BundleClassLoader.class, bundleWiring);
+        assertNotNull(bundleClassLoader);
+
+        try {
+            Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
+            assertNotNull(result);
+            verify(importedPkgs, never()).values();
+            verify(requiredPkgs, never()).values();
+            assertTrue(getAccessorCache(bundleWiring).containsKey(classToBeLoaded));
+            assertTrue(getAccessorCache(bundleWiring).get(classToBeLoaded) == bootDelegateClassLoader);
+        } catch (Exception e) {
+            fail();
+        }
+
+        //now make sure subsequent class loading happens from boot delegation
+        Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
+        assertNotNull(result);
+        //makes sure the look up cache is accessed and the class is loaded via boot delegation
+        verify(importedPkgs, never()).values();
+        verify(requiredPkgs, never()).values();
+    }
+
     @SuppressWarnings("rawtypes")
     private byte[] createTestClassBytes(Class testClass, String testClassAsPath)
             throws IOException



Mime
View raw message