[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