[jboss-svn-commits] JBoss Common SVN: r3103 - in common-core/trunk/src: test/java/org/jboss/test/util/test and 1 other directory.
jboss-svn-commits at lists.jboss.org
jboss-svn-commits at lists.jboss.org
Thu Apr 9 10:48:09 EDT 2009
Author: bstansberry at jboss.com
Date: 2009-04-09 10:48:08 -0400 (Thu, 09 Apr 2009)
New Revision: 3103
Added:
common-core/trunk/src/main/java/org/jboss/util/lock/
common-core/trunk/src/test/java/org/jboss/test/util/test/TimedCachePolicyClassLoaderLeakTestCase.java
Modified:
common-core/trunk/src/main/java/org/jboss/util/TimedCachePolicy.java
Log:
[JBCOMMON-50] Don't leak TCCL to Timer thread
Modified: common-core/trunk/src/main/java/org/jboss/util/TimedCachePolicy.java
===================================================================
--- common-core/trunk/src/main/java/org/jboss/util/TimedCachePolicy.java 2009-04-06 12:57:28 UTC (rev 3102)
+++ common-core/trunk/src/main/java/org/jboss/util/TimedCachePolicy.java 2009-04-09 14:48:08 UTC (rev 3103)
@@ -21,6 +21,8 @@
*/
package org.jboss.util;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -30,6 +32,9 @@
import java.util.Timer;
import java.util.TimerTask;
+import org.jboss.logging.Logger;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+
/** An implementation of a timed cache. This is a cache whose entries have a
limited lifetime with the ability to refresh their lifetime. The entries
managed by the cache implement the TimedCachePolicy.TimedEntry interface. If
@@ -48,6 +53,31 @@
extends TimerTask /* A legacy base class that is no longer used as this level */
implements CachePolicy
{
+ /**
+ * Name of system property that this class consults to determine what
+ * classloader to assign to its static {@link Timer}'s thread.
+ */
+ public static final String TIMER_CLASSLOADER_PROPERTY = "jboss.common.timedcachepolicy.timer.classloader";
+
+ /**
+ * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
+ * {@link ClassLoader#getSystemClassLoader() system classloader} should
+ * be used. This is the default value if the system property is not set.
+ */
+ public static final String TIMER_CLASSLOADER_SYSTEM = "system";
+ /**
+ * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
+ * {@link Class#getClassLoader() classloader that loaded this class} should
+ * be used.
+ */
+ public static final String TIMER_CLASSLOADER_CURRENT = "current";
+ /**
+ * Value for {@link #TIMER_CLASSLOADER_PROPERTY} indicating the
+ * {@link Thread#getContextClassLoader() thread context classloader}
+ * in effect when this class is loaded should be used.
+ */
+ public static final String TIMER_CLASSLOADER_CONTEXT = "context";
+
/** The interface that cache entries support.
*/
public static interface TimedEntry
@@ -79,9 +109,60 @@
*/
public Object getValue();
}
+
+ private static final Logger log = Logger.getLogger(TimedCachePolicy.class);
- protected static Timer resolutionTimer = new Timer(true);
+ protected static Timer resolutionTimer;
+ static
+ {
+ // Don't leak the TCCL to the resolutionTimer thread
+ ContextClassLoaderSwitcher.SwitchContext clSwitchContext = null;
+ try
+ {
+ // See if the user configured what classloader they want
+ String timerCl = AccessController.doPrivileged(new PrivilegedAction<String>()
+ {
+ public String run()
+ {
+ return System.getProperty(TIMER_CLASSLOADER_PROPERTY, TIMER_CLASSLOADER_SYSTEM);
+ }
+ });
+
+ if (TIMER_CLASSLOADER_CONTEXT.equalsIgnoreCase(timerCl) == false)
+ {
+ ContextClassLoaderSwitcher clSwitcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+ if (TIMER_CLASSLOADER_CURRENT.equalsIgnoreCase(timerCl))
+ {
+ // Switches the TCCL to this class' classloader
+ clSwitchContext = clSwitcher.getSwitchContext(TimedCachePolicy.class.getClassLoader());
+ }
+ else
+ {
+ if (TIMER_CLASSLOADER_SYSTEM.equalsIgnoreCase(timerCl) == false)
+ {
+ log.warn("Unknown value " + timerCl + " found for property " +
+ TIMER_CLASSLOADER_PROPERTY + " -- using the system classloader");
+ }
+ clSwitchContext = clSwitcher.getSwitchContext(ClassLoader.getSystemClassLoader());
+ }
+ }
+ resolutionTimer = new Timer(true);
+ }
+ catch (SecurityException e)
+ {
+ // For backward compatibility, don't blow up, just risk leaking the TCCL
+ // TODO log a WARN or something?
+ resolutionTimer = new Timer(true);
+ }
+ finally
+ {
+ // Restores the TCCL
+ if (clSwitchContext != null)
+ clSwitchContext.reset();
+ }
+ }
+
/** The map of cached TimedEntry objects. */
protected Map entryMap;
/** The lifetime in seconds to use for objects inserted
Added: common-core/trunk/src/test/java/org/jboss/test/util/test/TimedCachePolicyClassLoaderLeakTestCase.java
===================================================================
--- common-core/trunk/src/test/java/org/jboss/test/util/test/TimedCachePolicyClassLoaderLeakTestCase.java (rev 0)
+++ common-core/trunk/src/test/java/org/jboss/test/util/test/TimedCachePolicyClassLoaderLeakTestCase.java 2009-04-09 14:48:08 UTC (rev 3103)
@@ -0,0 +1,199 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.test.util.test;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.util.TimedCachePolicy;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for JBCOMMON-50.
+ *
+ * @author Brian Stansberry
+ */
+public class TimedCachePolicyClassLoaderLeakTestCase extends TestCase
+{
+ protected void tearDown() throws Exception
+ {
+ System.clearProperty(TimedCachePolicy.TIMER_CLASSLOADER_PROPERTY);
+ }
+
+ public void testUseSystemClassLoader() throws Exception
+ {
+ classLoaderLeaktoTimerTest(TimedCachePolicy.TIMER_CLASSLOADER_SYSTEM, false);
+ }
+
+ public void testUseCurrentClassLoader() throws Exception
+ {
+ classLoaderLeaktoTimerTest(TimedCachePolicy.TIMER_CLASSLOADER_CURRENT, false);
+ }
+
+ public void testUseContextClassLoader() throws Exception
+ {
+ // Here we expect to leak the CL
+ classLoaderLeaktoTimerTest(TimedCachePolicy.TIMER_CLASSLOADER_CONTEXT, true);
+ }
+
+ public void testUseBogusClassLoader() throws Exception
+ {
+ classLoaderLeaktoTimerTest("bogus", false);
+ }
+
+ private void classLoaderLeaktoTimerTest(String timerCL, boolean expectLeak) throws Exception
+ {
+ ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader isolated = new IsolatedTimedCachePolicyClassLoader(origClassLoader);
+ ClassLoader cl = new ClassLoader(isolated){};
+ WeakReference<ClassLoader> clRef = new WeakReference<ClassLoader>(cl);
+ Thread.currentThread().setContextClassLoader(cl);
+ Object policy = null;
+ try
+ {
+ policy = createTimedCachePolicy(timerCL);
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(origClassLoader);
+ }
+
+ cl = null;
+ System.gc();
+ if (expectLeak)
+ {
+ assertNotNull("ClassLoader not collected", clRef.get());
+ }
+ else
+ {
+ assertNull("ClassLoader collected", clRef.get());
+ }
+
+ if (policy != null)
+ {
+ destroyTimedCachePolicy(policy);
+ }
+ }
+
+ private static Object createTimedCachePolicy(String timerCL) throws Exception
+ {
+ System.setProperty(TimedCachePolicy.TIMER_CLASSLOADER_PROPERTY, timerCL);
+ Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("org.jboss.util.TimedCachePolicy");
+ Object obj = clazz.newInstance();
+ Method create = clazz.getDeclaredMethod("create", new Class[0]);
+ Method start = clazz.getDeclaredMethod("start", new Class[0]);
+ Method insert = clazz.getDeclaredMethod("insert", new Class[]{Object.class, Object.class});
+ create.invoke(obj, new Object[0]);
+ start.invoke(obj, new Object[0]);
+ insert.invoke(obj, new Object[]{new Object(), new Object()});
+ return obj;
+ }
+
+ private static void destroyTimedCachePolicy(Object policy) throws Exception
+ {
+ Class<?> clazz = policy.getClass();
+ Method stop = clazz.getDeclaredMethod("stop", new Class[0]);
+ Method destroy = clazz.getDeclaredMethod("destroy", new Class[0]);
+ stop.invoke(policy, new Object[0]);
+ destroy.invoke(policy, new Object[0]);
+
+ }
+
+ private static class IsolatedTimedCachePolicyClassLoader extends ClassLoader
+ {
+ private Map<String, Class<?>> clazzes = new HashMap<String, Class<?>>();
+
+ public IsolatedTimedCachePolicyClassLoader()
+ {
+ this(Thread.currentThread().getContextClassLoader());
+ }
+
+ public IsolatedTimedCachePolicyClassLoader(ClassLoader parent)
+ {
+ super(parent);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+ {
+ if (name.startsWith("org.jboss.util.TimedCachePolicy"))
+ {
+ Class<?> c = findClass(name);
+ if (resolve)
+ {
+ resolveClass(c);
+ }
+ return c;
+ }
+ else
+ {
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException
+ {
+ if (name.startsWith("org.jboss.util.TimedCachePolicy"))
+ {
+ Class<?> clazz = clazzes.get(name);
+ if (clazz == null)
+ {
+ String path = name.replace('.', '/').concat(".class");
+ InputStream stream = getParent().getResourceAsStream(path);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] input = new byte[1024];
+ int read = 0;
+ try
+ {
+ while ((read = stream.read(input)) > -1)
+ {
+ baos.write(input, 0, read);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ byte[] bytes = baos.toByteArray();
+ clazz = defineClass(name, bytes, 0, bytes.length);
+ clazzes.put(name, clazz);
+ }
+ return clazz;
+ }
+ else
+ {
+ return super.findClass(name);
+ }
+ }
+
+ }
+
+}
Property changes on: common-core/trunk/src/test/java/org/jboss/test/util/test/TimedCachePolicyClassLoaderLeakTestCase.java
___________________________________________________________________
Name: svn:keywords
+
More information about the jboss-svn-commits
mailing list