rdonkin 2004/11/22 14:50:07
Modified: logging/optional/src/test/org/apache/commons/logging
LogFactoryTest.java
Log:
Improved test cases for WeakHashMap classloading. Contributed by Brian
Stansberry.
Revision Changes Path
1.3 +146 -96
jakarta-commons/logging/optional/src/test/org/apache/commons/logging/LogFactoryTest.java
Index: LogFactoryTest.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/logging/optional/src/test/org/apache/commons/logging/LogFactoryTest.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- LogFactoryTest.java 17 Nov 2004 23:23:21 -0000 1.2
+++ LogFactoryTest.java 22 Nov 2004 22:50:07 -0000 1.3
@@ -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,119 +49,97 @@
* 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;
+ public void testHoldFactories() throws Exception
+ {
+ // 1) Basic test
- // Run the gc, confirming that the original 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
- 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;
- }
- }
+ // 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 LogFactory is eventually removed from the map
- * after its creating ClassLoader is garbage collected.
+ * Tests that a ClassLoader is eventually removed from the map
+ * after all hard references to it are removed.
*/
- public void testReleaseFactories()
+ public void testReleaseClassLoader() throws Exception
{
- // Create a temporary classloader
- ClassLoader childLoader = new ClassLoader() {};
+ // 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);
-
- // 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);
+ 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);
+
+ // 2) Test using an isolated classloader a la a web app
- // Remove any hard reference to the childLoader and the factory
+ 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 <code>failOnRelease</code>.
+ */
+ 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;
- } else {
- // create garbage:
- byte[] b;
- try {
- b = new byte[bytz];
- bytz = bytz * 2;
+ if(reference.get() == null) {
+ if (failOnRelease) {
+ fail("reference released");
}
- catch (OutOfMemoryError oom) {
- // Doing this is probably a no-no, but it seems to work
;-)
- b = null;
- System.gc();
- fail("OutOfMemory before childLoader released.");
+ else {
+ break;
}
- }
- }
-
- // 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;
@@ -169,14 +150,16 @@
catch (OutOfMemoryError oom) {
// Doing this is probably a no-no, but it seems to work
;-)
b = null;
- bytz = 2; // start over
System.gc();
+ if (failOnRelease) {
+ break;
+ }
+ fail("OutOfMemory before reference released.");
}
}
}
-
- }
-
+ }
+
protected void setUp() throws Exception {
// Preserve the original classloader and factory implementation
// class so we can restore them when we are done
@@ -205,6 +188,73 @@
}
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);
+ }
+ }
+
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]