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
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
|
|
||||||
package org.apache.commons.logging;
|
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.lang.ref.WeakReference;
|
||||||
import java.util.Hashtable;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
@@ -46,84 +49,97 @@ public class LogFactoryTest extends TestCase {
|
|||||||
* Tests that LogFactories are not removed from the map
|
* Tests that LogFactories are not removed from the map
|
||||||
* if their creating ClassLoader is still alive.
|
* if their creating ClassLoader is still alive.
|
||||||
*/
|
*/
|
||||||
public void testHoldFactories()
|
public void testHoldFactories() throws Exception
|
||||||
{
|
{
|
||||||
// Get a factory and create a WeakReference to it that
|
// 1) Basic test
|
||||||
// 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
|
// Get a weak reference to the factory using the classloader.
|
||||||
factory = null;
|
// When this reference is cleared we know the factory has been
|
||||||
|
// cleared from LogFactory.factories as well
|
||||||
// Run the gc, confirming that the original factory
|
WeakReference weakFactory = loadFactoryFromContextClassLoader();
|
||||||
|
// Run the gc, confirming that the factory
|
||||||
// is not dropped from the map even though there are
|
// is not dropped from the map even though there are
|
||||||
// no other references to it
|
// no other references to it
|
||||||
int iterations = 0;
|
checkRelease(weakFactory, true);
|
||||||
int bytz = 2;
|
|
||||||
while(iterations++ < MAX_GC_ITERATIONS) {
|
|
||||||
System.gc();
|
|
||||||
|
|
||||||
assertNotNull("LogFactory released while ClassLoader still active.",
|
// 2) Test using an isolated classloader a la a web app
|
||||||
weakFactory.get());
|
|
||||||
|
|
||||||
// create garbage:
|
// Create a classloader that isolates commons-logging
|
||||||
byte[] b;
|
ClassLoader childLoader = new IsolatedClassLoader(origLoader);
|
||||||
try {
|
Thread.currentThread().setContextClassLoader(childLoader);
|
||||||
b = new byte[bytz];
|
weakFactory = loadFactoryFromContextClassLoader();
|
||||||
bytz = bytz * 2;
|
Thread.currentThread().setContextClassLoader(origLoader);
|
||||||
}
|
// At this point we still have a reference to childLoader,
|
||||||
catch (OutOfMemoryError oom) {
|
// so the factory should not be cleared
|
||||||
// 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 ;-)
|
checkRelease(weakFactory, true);
|
||||||
b = null;
|
|
||||||
System.gc();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that a LogFactory is eventually removed from the map
|
* Tests that a ClassLoader is eventually removed from the map
|
||||||
* after its creating ClassLoader is garbage collected.
|
* after all hard references to it are removed.
|
||||||
*/
|
*/
|
||||||
public void testReleaseFactories()
|
public void testReleaseClassLoader() throws Exception
|
||||||
{
|
{
|
||||||
// Create a temporary classloader
|
// 1) Test of a child classloader that follows the Java2
|
||||||
ClassLoader childLoader = new ClassLoader() {};
|
// delegation model (e.g. an EJB module classloader)
|
||||||
Thread.currentThread().setContextClassLoader(childLoader);
|
|
||||||
|
|
||||||
// Get a factory using the child loader.
|
// Create a classloader that delegates to its parent
|
||||||
LogFactory factory = LogFactory.getFactory();
|
ClassLoader childLoader = new ClassLoader() {};
|
||||||
// Hold a WeakReference to the factory. When this reference
|
// Get a weak reference to the factory using the classloader.
|
||||||
// is cleared we know the factory has been cleared from
|
// When this reference is cleared we know the factory has been
|
||||||
// LogFactory.factories as well
|
// cleared from LogFactory.factories as well
|
||||||
WeakReference weakFactory = new WeakReference(factory);
|
Thread.currentThread().setContextClassLoader(childLoader);
|
||||||
|
loadFactoryFromContextClassLoader();
|
||||||
|
Thread.currentThread().setContextClassLoader(origLoader);
|
||||||
|
|
||||||
// Get a WeakReference to the child loader so we know when it
|
// Get a WeakReference to the child loader so we know when it
|
||||||
// has been gc'ed
|
// 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;
|
||||||
|
|
||||||
// Remove any hard reference to the childLoader and the factory
|
// 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
|
||||||
|
|
||||||
|
childLoader = new IsolatedClassLoader(origLoader);
|
||||||
|
Thread.currentThread().setContextClassLoader(childLoader);
|
||||||
|
loadFactoryFromContextClassLoader();
|
||||||
Thread.currentThread().setContextClassLoader(origLoader);
|
Thread.currentThread().setContextClassLoader(origLoader);
|
||||||
childLoader = null;
|
weakLoader = new WeakReference(childLoader);
|
||||||
factory = null;
|
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 iterations = 0;
|
||||||
int bytz = 2;
|
int bytz = 2;
|
||||||
while(true) {
|
while(true) {
|
||||||
System.gc();
|
System.gc();
|
||||||
if(iterations++ > MAX_GC_ITERATIONS){
|
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) {
|
if(reference.get() == null) {
|
||||||
break;
|
if (failOnRelease) {
|
||||||
|
fail("reference released");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// create garbage:
|
// create garbage:
|
||||||
byte[] b;
|
byte[] b;
|
||||||
@@ -135,46 +151,13 @@ public class LogFactoryTest extends TestCase {
|
|||||||
// Doing this is probably a no-no, but it seems to work ;-)
|
// Doing this is probably a no-no, but it seems to work ;-)
|
||||||
b = null;
|
b = null;
|
||||||
System.gc();
|
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 {
|
protected void setUp() throws Exception {
|
||||||
@@ -207,4 +190,71 @@ public class LogFactoryTest extends TestCase {
|
|||||||
super.tearDown();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user