From 0f88d1e30a3b43235779ddb3600261e904f1f136 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 8 May 2014 21:03:38 +0200 Subject: [PATCH] fix possible class loader leak --- .../java/sonia/scm/boot/BootstrapFilter.java | 44 +++++-- .../sonia/scm/boot/BootstrapListener.java | 120 ++++++++++++------ .../java/sonia/scm/boot/BootstrapUtil.java | 13 ++ 3 files changed, 127 insertions(+), 50 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapFilter.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapFilter.java index 7ece6b4954..d68552db94 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapFilter.java @@ -72,12 +72,23 @@ public class BootstrapFilter implements Filter @Override public void destroy() { - if (classLoader != null) - { - Thread.currentThread().setContextClassLoader(classLoader); - } + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - guiceFilter.destroy(); + try + { + if (classLoader != null) + { + Thread.currentThread().setContextClassLoader(classLoader); + } + + logger.debug("destroy guice filter"); + + guiceFilter.destroy(); + } + finally + { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } } /** @@ -93,15 +104,24 @@ public class BootstrapFilter implements Filter */ @Override public void doFilter(ServletRequest request, ServletResponse response, - FilterChain chain) - throws IOException, ServletException + FilterChain chain) + throws IOException, ServletException { - if (classLoader != null) - { - Thread.currentThread().setContextClassLoader(classLoader); - } + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - guiceFilter.doFilter(request, response, chain); + try + { + if (classLoader != null) + { + Thread.currentThread().setContextClassLoader(classLoader); + } + + guiceFilter.doFilter(request, response, chain); + } + finally + { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } } /** diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapListener.java index cf922b02a2..24814e88c2 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapListener.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapListener.java @@ -35,14 +35,19 @@ package sonia.scm.boot; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.Lists; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; +import sonia.scm.util.ClassLoaders; +import sonia.scm.util.IOUtil; //~--- JDK imports ------------------------------------------------------------ +import java.io.Closeable; import java.io.File; import java.net.MalformedURLException; @@ -51,6 +56,7 @@ import java.net.URL; import java.util.LinkedList; import java.util.List; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -89,8 +95,28 @@ public class BootstrapListener implements ServletContextListener { if (scmContextListener != null) { + logger.info("destroy scm context listener"); scmContextListener.contextDestroyed(sce); } + + ServletContext servletContext = sce.getServletContext(); + ClassLoader classLoader = BootstrapUtil.getClassLoader(servletContext); + + if (classLoader != null) + { + if (classLoader instanceof Closeable) + { + logger.info("close plugin class loader"); + IOUtil.close((Closeable) classLoader); + } + + logger.debug("remove plugin class loader from servlet context"); + BootstrapUtil.removeClassLoader(servletContext); + } + else + { + logger.debug("plugin class loader is not available"); + } } /** @@ -110,6 +136,44 @@ public class BootstrapListener implements ServletContextListener context.getStage()); } + ClassLoader classLoader = createClassLoader(context); + + if (classLoader != null) + { + if (logger.isInfoEnabled()) + { + logger.info("try to use ScmBootstrapClassLoader"); + } + + scmContextListener = BootstrapUtil.loadClass(classLoader, + ServletContextListener.class, LISTENER); + BootstrapUtil.setClassLoader(sce.getServletContext(), classLoader); + } + + if (scmContextListener == null) + { + if (logger.isWarnEnabled()) + { + logger.warn("fallback to default classloader"); + } + + scmContextListener = + BootstrapUtil.loadClass(ServletContextListener.class, LISTENER); + } + + initializeContext(classLoader, scmContextListener, sce); + } + + /** + * Method description + * + * + * @param context + * + * @return + */ + private ClassLoader createClassLoader(SCMContextProvider context) + { ClassLoader classLoader = null; File pluginDirectory = new File(context.getBaseDirectory(), PLUGIN_DIRECTORY); @@ -144,31 +208,7 @@ public class BootstrapListener implements ServletContextListener logger.debug("no plugin directory found"); } - if (classLoader != null) - { - if (logger.isInfoEnabled()) - { - logger.info("try to use ScmBootstrapClassLoader"); - } - - scmContextListener = BootstrapUtil.loadClass(classLoader, - ServletContextListener.class, LISTENER); - Thread.currentThread().setContextClassLoader(classLoader); - BootstrapUtil.setClassLoader(sce.getServletContext(), classLoader); - } - - if (scmContextListener == null) - { - if (logger.isWarnEnabled()) - { - logger.warn("fallback to default classloader"); - } - - scmContextListener = - BootstrapUtil.loadClass(ServletContextListener.class, LISTENER); - } - - scmContextListener.contextInitialized(sce); + return classLoader; } /** @@ -188,7 +228,7 @@ public class BootstrapListener implements ServletContextListener logger.debug("create classloader from plugin classpath"); } - List classpathURLs = new LinkedList(); + List classpathURLs = Lists.newLinkedList(); for (String path : classpath) { @@ -224,32 +264,36 @@ public class BootstrapListener implements ServletContextListener } return BootstrapUtil.createClassLoader(classpathURLs, - getParentClassLoader()); + ClassLoaders.getContextClassLoader(BootstrapListener.class)); } - //~--- get methods ---------------------------------------------------------- - /** * Method description * * - * @return + * @param classLoader + * @param listener + * @param sce */ - private ClassLoader getParentClassLoader() + private void initializeContext(ClassLoader classLoader, + ServletContextListener listener, ServletContextEvent sce) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader == null) + try { - if (logger.isWarnEnabled()) + if (classLoader != null) { - logger.warn("could not use context classloader, try to use default"); + Thread.currentThread().setContextClassLoader(classLoader); } - classLoader = BootstrapListener.class.getClassLoader(); + logger.info("initialize scm context listener"); + listener.contextInitialized(sce); + } + finally + { + Thread.currentThread().setContextClassLoader(oldClassLoader); } - - return classLoader; } //~--- fields --------------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapUtil.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapUtil.java index cb2f842595..87db199d7a 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapUtil.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapUtil.java @@ -235,4 +235,17 @@ public final class BootstrapUtil { context.setAttribute(CLASSLOADER, classLoader); } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param context + */ + public static void removeClassLoader(ServletContext context) + { + context.removeAttribute(CLASSLOADER); + } }