From 00e49fe6cfc0b2edcb0e6d4890436bdf2e5eb441 Mon Sep 17 00:00:00 2001 From: Robert Burrell Donkin Date: Mon, 22 Nov 2004 22:50:51 +0000 Subject: [PATCH] Improved test cases for WeakHashMap classloading. Contributed by Brian Stansberry. git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/logging/trunk@139060 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/logging/IFactoryCreator.java | 36 +++ .../commons/logging/LogFactoryTest.java | 256 +++++++++++------- .../commons/logging/SubDeploymentClass.java | 41 +++ 3 files changed, 230 insertions(+), 103 deletions(-) create mode 100644 optional/src/test/org/apache/commons/logging/IFactoryCreator.java create mode 100644 optional/src/test/org/apache/commons/logging/SubDeploymentClass.java diff --git a/optional/src/test/org/apache/commons/logging/IFactoryCreator.java b/optional/src/test/org/apache/commons/logging/IFactoryCreator.java new file mode 100644 index 0000000..5ed3ae8 --- /dev/null +++ b/optional/src/test/org/apache/commons/logging/IFactoryCreator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging; + +import java.lang.ref.WeakReference; + + + +/** + * Interface implemented by SubDeploymentClass. The LogFactoryTest's + * IsolatedClassLoader will delegate loading of this interface to the + * test runner's classloader, so the test can interact with + * SubDeploymentClass via this interface. + * + * @author bstansberry + * + * @see LogFactoryTest + */ +public interface IFactoryCreator { + + WeakReference getWeakFactory(); + +} diff --git a/optional/src/test/org/apache/commons/logging/LogFactoryTest.java b/optional/src/test/org/apache/commons/logging/LogFactoryTest.java index cd1db61..67c9e85 100644 --- a/optional/src/test/org/apache/commons/logging/LogFactoryTest.java +++ b/optional/src/test/org/apache/commons/logging/LogFactoryTest.java @@ -17,8 +17,11 @@ package org.apache.commons.logging; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.lang.ref.WeakReference; -import java.util.Hashtable; import junit.framework.TestCase; @@ -46,84 +49,97 @@ public class LogFactoryTest extends TestCase { * Tests that LogFactories are not removed from the map * if their creating ClassLoader is still alive. */ - public void testHoldFactories() - { - // Get a factory and create a WeakReference to it that - // we can check to see if the factory has been removed - // from LogFactory.properties - LogFactory factory = LogFactory.getFactory(); - WeakReference weakFactory = new WeakReference(factory); - - // Remove any hard reference to the factory - factory = null; - - // Run the gc, confirming that the original factory - // is not dropped from the map even though there are - // no other references to it - int iterations = 0; - int bytz = 2; - while(iterations++ < MAX_GC_ITERATIONS) { - System.gc(); - - assertNotNull("LogFactory released while ClassLoader still active.", - weakFactory.get()); - - // create garbage: - byte[] b; - try { - b = new byte[bytz]; - bytz = bytz * 2; - } - catch (OutOfMemoryError oom) { - // This error means the test passed, as it means the LogFactory - // was never released. So, we have to catch and deal with it - - // Doing this is probably a no-no, but it seems to work ;-) - b = null; - System.gc(); - break; - } - } - } - - /** - * Tests that a LogFactory is eventually removed from the map - * after its creating ClassLoader is garbage collected. - */ - public void testReleaseFactories() + public void testHoldFactories() throws Exception { - // Create a temporary classloader - ClassLoader childLoader = new ClassLoader() {}; - Thread.currentThread().setContextClassLoader(childLoader); + // 1) Basic test - // Get a factory using the child loader. - LogFactory factory = LogFactory.getFactory(); - // Hold a WeakReference to the factory. When this reference - // is cleared we know the factory has been cleared from - // LogFactory.factories as well - WeakReference weakFactory = new WeakReference(factory); + // Get a weak reference to the factory using the classloader. + // When this reference is cleared we know the factory has been + // cleared from LogFactory.factories as well + WeakReference weakFactory = loadFactoryFromContextClassLoader(); + // Run the gc, confirming that the factory + // is not dropped from the map even though there are + // no other references to it + checkRelease(weakFactory, true); + + // 2) Test using an isolated classloader a la a web app + + // Create a classloader that isolates commons-logging + ClassLoader childLoader = new IsolatedClassLoader(origLoader); + Thread.currentThread().setContextClassLoader(childLoader); + weakFactory = loadFactoryFromContextClassLoader(); + Thread.currentThread().setContextClassLoader(origLoader); + // At this point we still have a reference to childLoader, + // so the factory should not be cleared + + checkRelease(weakFactory, true); + } + + /** + * Tests that a ClassLoader is eventually removed from the map + * after all hard references to it are removed. + */ + public void testReleaseClassLoader() throws Exception + { + // 1) Test of a child classloader that follows the Java2 + // delegation model (e.g. an EJB module classloader) + + // Create a classloader that delegates to its parent + ClassLoader childLoader = new ClassLoader() {}; + // Get a weak reference to the factory using the classloader. + // When this reference is cleared we know the factory has been + // cleared from LogFactory.factories as well + Thread.currentThread().setContextClassLoader(childLoader); + loadFactoryFromContextClassLoader(); + Thread.currentThread().setContextClassLoader(origLoader); // Get a WeakReference to the child loader so we know when it // has been gc'ed - WeakReference weakLoader = new WeakReference(childLoader); + WeakReference weakLoader = new WeakReference(childLoader); + // Remove any hard reference to the childLoader or the factory creator + childLoader = null; + + // Run the gc, confirming that childLoader is dropped from the map + checkRelease(weakLoader, false); - // Remove any hard reference to the childLoader and the factory + // 2) Test using an isolated classloader a la a web app + + childLoader = new IsolatedClassLoader(origLoader); + Thread.currentThread().setContextClassLoader(childLoader); + loadFactoryFromContextClassLoader(); Thread.currentThread().setContextClassLoader(origLoader); - childLoader = null; - factory = null; + weakLoader = new WeakReference(childLoader); + childLoader = null; // somewhat equivalent to undeploying a webapp + + checkRelease(weakLoader, false); + + } + + /** + * Repeatedly run the gc, checking whether the given WeakReference + * is not cleared and failing or succeeding based on + * parameter failOnRelease. + */ + private void checkRelease(WeakReference reference, boolean failOnRelease) { - // Run the gc, confirming that the original childLoader - // is dropped from the map int iterations = 0; int bytz = 2; while(true) { System.gc(); if(iterations++ > MAX_GC_ITERATIONS){ - fail("Max iterations reached before childLoader released."); + if (failOnRelease) { + break; + } + fail("Max iterations reached before reference released."); } - if(weakLoader.get() == null) { - break; + if(reference.get() == null) { + if (failOnRelease) { + fail("reference released"); + } + else { + break; + } } else { // create garbage: byte[] b; @@ -135,48 +151,15 @@ public class LogFactoryTest extends TestCase { // Doing this is probably a no-no, but it seems to work ;-) b = null; System.gc(); - fail("OutOfMemory before childLoader released."); + if (failOnRelease) { + break; + } + fail("OutOfMemory before reference released."); } } } - - // Confirm that the original factory is removed from the map - // within the maximum allowed number of calls to put() + - // the maximum number of subsequent gc iterations - iterations = 0; - while(true) { - System.gc(); - if(iterations++ > WeakHashtable.MAX_PUTS_BEFORE_PURGE + MAX_GC_ITERATIONS){ - Hashtable table = LogFactory.factories; - fail("Max iterations reached before factory released."); - } - - // Create a new child loader and use it to add to the map. - ClassLoader newChildLoader = new ClassLoader() {}; - Thread.currentThread().setContextClassLoader(newChildLoader); - LogFactory.getFactory(); - Thread.currentThread().setContextClassLoader(origLoader); - - if(weakFactory.get() == null) { - break; - } else { - // create garbage: - byte[] b; - try { - b = new byte[bytz]; - bytz = bytz * 2; - } - catch (OutOfMemoryError oom) { - // Doing this is probably a no-no, but it seems to work ;-) - b = null; - bytz = 2; // start over - System.gc(); - } - } - } - - } - + } + protected void setUp() throws Exception { // Preserve the original classloader and factory implementation // class so we can restore them when we are done @@ -207,4 +190,71 @@ public class LogFactoryTest extends TestCase { super.tearDown(); } + private static WeakReference loadFactoryFromContextClassLoader() + throws Exception { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Class clazz = loader.loadClass(SubDeploymentClass.class.getName()); + IFactoryCreator creator = (IFactoryCreator) clazz.newInstance(); + return creator.getWeakFactory(); + } + + /** + * A ClassLoader that mimics the operation of a web app classloader + * by not delegating some calls to its parent. + * + * In this case it does not delegate loading commons-logging classes, + * acting as if commons-logging were in WEB-INF/lib. However, it does + * delegate loading of IFactoryCreator, thus allowing this class to + * interact with SubDeploymentClass via IFactoryCreator. + */ + private static final class IsolatedClassLoader extends ClassLoader { + + private IsolatedClassLoader(ClassLoader parent) { + super(parent); + } + + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + if (name != null && name.startsWith("org.apache.commons.logging") + && "org.apache.commons.logging.IFactoryCreator".equals(name) == false) { + // First, check if the class has already been loaded + Class c = findClass(name); + + if (resolve) { + resolveClass(c); + } + return c; + } + else { + return super.loadClass(name, resolve); + } + } + + protected Class findClass(String name) throws ClassNotFoundException { + if (name != null && name.startsWith("org.apache.commons.logging") + && "org.apache.commons.logging.IFactoryCreator".equals(name) == false) { + try { + InputStream is = getResourceAsStream( name.replace('.','/').concat(".class")); + byte[] bytes = new byte[1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + int read; + while ((read = is.read(bytes)) > -1) { + baos.write(bytes, 0, read); + } + bytes = baos.toByteArray(); + return this.defineClass(name, bytes, 0, bytes.length); + } catch (FileNotFoundException e) { + throw new ClassNotFoundException("cannot find " + name, e); + } catch (IOException e) { + throw new ClassNotFoundException("cannot read " + name, e); + } + } + else { + return super.findClass(name); + } + } + + } + } diff --git a/optional/src/test/org/apache/commons/logging/SubDeploymentClass.java b/optional/src/test/org/apache/commons/logging/SubDeploymentClass.java new file mode 100644 index 0000000..ac6bb73 --- /dev/null +++ b/optional/src/test/org/apache/commons/logging/SubDeploymentClass.java @@ -0,0 +1,41 @@ +/* + * Copyright 2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.logging; + +import java.lang.ref.WeakReference; + +/** + * Class that mocks a user class deployed in a sub-deployment that + * interacts with LogFactory. + * + * @author bstansberry + * + * @see LogFactoryTest + */ +public class SubDeploymentClass implements IFactoryCreator { + + private WeakReference weakFactory = null; + + public SubDeploymentClass() { + LogFactory factory = LogFactory.getFactory(); + weakFactory = new WeakReference(factory); + } + + public WeakReference getWeakFactory() { + return weakFactory; + } + +}