Index: CHANGES.txt =================================================================== --- CHANGES.txt (revision 908867) +++ CHANGES.txt (working copy) @@ -107,6 +107,11 @@ * LUCENE-2249: ParallelMultiSearcher should shut down thread pool on close. (Martin Traverso via Uwe Schindler) +* LUCENE-2260: Fixed AttributeSource to not hold a strong + reference to the Attribute/AttributeImpl classes which prevents + unloading of custom attributes loaded by other classloaders + (e.g. in Solr plugins). (Uwe Schindler) + New features * LUCENE-2128: Parallelized fetching document frequencies during weight Index: src/java/org/apache/lucene/util/AttributeSource.java =================================================================== --- src/java/org/apache/lucene/util/AttributeSource.java (revision 908867) +++ src/java/org/apache/lucene/util/AttributeSource.java (working copy) @@ -17,11 +17,12 @@ * limitations under the License. */ +import java.lang.ref.WeakReference; import java.util.Collections; import java.util.NoSuchElementException; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.IdentityHashMap; +import java.util.WeakHashMap; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; @@ -54,8 +55,8 @@ public static final AttributeFactory DEFAULT_ATTRIBUTE_FACTORY = new DefaultAttributeFactory(); private static final class DefaultAttributeFactory extends AttributeFactory { - private static final IdentityHashMap, Class> attClassImplMap = - new IdentityHashMap, Class>(); + private static final WeakHashMap, WeakReference>> attClassImplMap = + new WeakHashMap, WeakReference>>(); private DefaultAttributeFactory() {} @@ -72,12 +73,15 @@ private static Class getClassForInterface(Class attClass) { synchronized(attClassImplMap) { - Class clazz = attClassImplMap.get(attClass); + final WeakReference> ref = attClassImplMap.get(attClass); + Class clazz = (ref == null) ? null : ref.get(); if (clazz == null) { try { attClassImplMap.put(attClass, - clazz = Class.forName(attClass.getName() + "Impl", true, attClass.getClassLoader()) - .asSubclass(AttributeImpl.class) + new WeakReference>( + clazz = Class.forName(attClass.getName() + "Impl", true, attClass.getClassLoader()) + .asSubclass(AttributeImpl.class) + ) ); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Could not find implementing class for " + attClass.getName()); @@ -173,8 +177,8 @@ } /** a cache that stores all interfaces for known implementation classes for performance (slow reflection) */ - private static final IdentityHashMap,LinkedList>> knownImplClasses = - new IdentityHashMap,LinkedList>>(); + private static final WeakHashMap,LinkedList>>> knownImplClasses = + new WeakHashMap,LinkedList>>>(); /** Expert: Adds a custom AttributeImpl instance with one or more Attribute interfaces. *

Please note: It is not guaranteed, that att is added to @@ -187,18 +191,20 @@ public void addAttributeImpl(final AttributeImpl att) { final Class clazz = att.getClass(); if (attributeImpls.containsKey(clazz)) return; - LinkedList> foundInterfaces; + LinkedList>> foundInterfaces; synchronized(knownImplClasses) { foundInterfaces = knownImplClasses.get(clazz); if (foundInterfaces == null) { - knownImplClasses.put(clazz, foundInterfaces = new LinkedList>()); + // we have a strong reference to the class instance holding all interfaces in the list (parameter "att"), + // so all WeakReferences are never evicted by GC + knownImplClasses.put(clazz, foundInterfaces = new LinkedList>>()); // find all interfaces that this attribute instance implements // and that extend the Attribute interface Class actClazz = clazz; do { for (Class curInterface : actClazz.getInterfaces()) { if (curInterface != Attribute.class && Attribute.class.isAssignableFrom(curInterface)) { - foundInterfaces.add(curInterface.asSubclass(Attribute.class)); + foundInterfaces.add(new WeakReference>(curInterface.asSubclass(Attribute.class))); } } actClazz = actClazz.getSuperclass(); @@ -207,7 +213,10 @@ } // add all interfaces of this AttributeImpl to the maps - for (Class curInterface : foundInterfaces) { + for (WeakReference> curInterfaceRef : foundInterfaces) { + final Class curInterface = curInterfaceRef.get(); + assert (curInterface != null) : + "We have a strong reference on the class holding the interfaces, so they should never get evicted"; // Attribute is a superclass of this interface if (!attributes.containsKey(curInterface)) { // invalidate state to force recomputation in captureState() Index: src/java/org/apache/lucene/util/VirtualMethod.java =================================================================== --- src/java/org/apache/lucene/util/VirtualMethod.java (revision 908867) +++ src/java/org/apache/lucene/util/VirtualMethod.java (working copy) @@ -20,7 +20,7 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; -import java.util.IdentityHashMap; +import java.util.WeakHashMap; import java.util.Set; /** @@ -64,8 +64,8 @@ private final Class baseClass; private final String method; private final Class[] parameters; - private final IdentityHashMap, Integer> cache = - new IdentityHashMap, Integer>(); + private final WeakHashMap, Integer> cache = + new WeakHashMap, Integer>(); /** * Creates a new instance for the given {@code baseClass} and method declaration.