mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 00:45:44 +01:00
make ClassLoaderLeakPreventorFactory configurable and mark BootstrapClassLoader as shutdown
This commit is contained in:
@@ -5,7 +5,29 @@ package sonia.scm.lifecycle.classloading;
|
|||||||
* find it in a heap dump.
|
* find it in a heap dump.
|
||||||
*/
|
*/
|
||||||
class BootstrapClassLoader extends ClassLoader {
|
class BootstrapClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker to find a BootstrapClassLoader, which is already shutdown.
|
||||||
|
*/
|
||||||
|
private boolean shutdown = false;
|
||||||
|
|
||||||
BootstrapClassLoader(ClassLoader webappClassLoader) {
|
BootstrapClassLoader(ClassLoader webappClassLoader) {
|
||||||
super(webappClassLoader);
|
super(webappClassLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the classloader was shutdown.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the classloader was shutdown
|
||||||
|
*/
|
||||||
|
boolean isShutdown() {
|
||||||
|
return shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the class loader as shutdown.
|
||||||
|
*/
|
||||||
|
void markAsShutdown() {
|
||||||
|
shutdown = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
||||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp;
|
||||||
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
|
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator;
|
||||||
|
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator;
|
||||||
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
|
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
|
||||||
import sonia.scm.lifecycle.LifeCycle;
|
import sonia.scm.lifecycle.LifeCycle;
|
||||||
import sonia.scm.plugin.ChildFirstPluginClassLoader;
|
import sonia.scm.plugin.ChildFirstPluginClassLoader;
|
||||||
@@ -16,9 +22,9 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and shutdown SCM-Manager ClassLoaders.
|
* Creates and shutdown SCM-Manager ClassLoaders.
|
||||||
@@ -27,23 +33,25 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
|
||||||
|
|
||||||
private final Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
|
private Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
|
||||||
|
|
||||||
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
||||||
private final ClassLoader webappClassLoader;
|
private final ClassLoader webappClassLoader;
|
||||||
|
|
||||||
private ClassLoader bootstrapClassLoader;
|
private BootstrapClassLoader bootstrapClassLoader;
|
||||||
private UnaryOperator<ClassLoader> classLoaderAppendListener = c -> c;
|
|
||||||
|
private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
|
||||||
|
@Override
|
||||||
|
public <C extends ClassLoader> C apply(C classLoader) {
|
||||||
|
return classLoader;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static ClassLoaderLifeCycle create() {
|
public static ClassLoaderLifeCycle create() {
|
||||||
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory();
|
ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
|
||||||
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
|
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader);
|
||||||
// the SunAwtAppContextInitiator causes a lot of exceptions and we use no awt
|
return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory);
|
||||||
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
|
|
||||||
// the MBeanCleanUp causes a Exception and we use no mbeans
|
|
||||||
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
|
|
||||||
return new ClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader(), classLoaderLeakPreventorFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
|
ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
|
||||||
@@ -51,12 +59,64 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
|
|||||||
this.webappClassLoader = initAndAppend(webappClassLoader);
|
this.webappClassLoader = initAndAppend(webappClassLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) {
|
||||||
|
// Should threads tied to the web app classloader be forced to stop at application shutdown?
|
||||||
|
boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads");
|
||||||
|
|
||||||
|
// Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
|
||||||
|
boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads");
|
||||||
|
|
||||||
|
// Should shutdown hooks registered from the application be executed at application shutdown?
|
||||||
|
boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks");
|
||||||
|
|
||||||
|
// No of milliseconds to wait for threads to finish execution, before stopping them.
|
||||||
|
int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
|
||||||
|
* If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
|
||||||
|
*/
|
||||||
|
int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT);
|
||||||
|
|
||||||
|
LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycle.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) );
|
||||||
|
LOG.info(" stopThreads = {}", stopThreads);
|
||||||
|
LOG.info(" stopTimerThreads = {}", stopTimerThreads);
|
||||||
|
LOG.info(" executeShutdownHooks = {}", executeShutdownHooks);
|
||||||
|
LOG.info(" threadWaitMs = {} ms", threadWaitMs);
|
||||||
|
LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs);
|
||||||
|
|
||||||
|
// use webapp classloader as safe base? or system?
|
||||||
|
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader);
|
||||||
|
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
|
||||||
|
|
||||||
|
final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class);
|
||||||
|
shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks);
|
||||||
|
shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs);
|
||||||
|
|
||||||
|
final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class);
|
||||||
|
stopThreadsCleanUp.setStopThreads(stopThreads);
|
||||||
|
stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads);
|
||||||
|
stopThreadsCleanUp.setThreadWaitMs(threadWaitMs);
|
||||||
|
|
||||||
|
// remove awt and imageio cleanup
|
||||||
|
classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class);
|
||||||
|
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
|
||||||
|
classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class);
|
||||||
|
classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class);
|
||||||
|
classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class);
|
||||||
|
|
||||||
|
// the MBeanCleanUp causes a Exception and we use no mbeans
|
||||||
|
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
|
||||||
|
|
||||||
|
return classLoaderLeakPreventorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
|
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setClassLoaderAppendListener(UnaryOperator<ClassLoader> classLoaderAppendListener) {
|
void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
|
||||||
this.classLoaderAppendListener = classLoaderAppendListener;
|
this.classLoaderAppendListener = classLoaderAppendListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,12 +144,17 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
|
|||||||
clap.shutdown();
|
clap.shutdown();
|
||||||
clap = classLoaders.poll();
|
clap = classLoaders.poll();
|
||||||
}
|
}
|
||||||
|
// be sure it is realy empty
|
||||||
|
classLoaders.clear();
|
||||||
|
classLoaders = new ArrayDeque<>();
|
||||||
|
|
||||||
|
bootstrapClassLoader.markAsShutdown();
|
||||||
bootstrapClassLoader = null;
|
bootstrapClassLoader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassLoader initAndAppend(ClassLoader originalClassLoader) {
|
private <T extends ClassLoader> T initAndAppend(T originalClassLoader) {
|
||||||
LOG.debug("init classloader {}", originalClassLoader);
|
LOG.debug("init classloader {}", originalClassLoader);
|
||||||
ClassLoader classLoader = classLoaderAppendListener.apply(originalClassLoader);
|
T classLoader = classLoaderAppendListener.apply(originalClassLoader);
|
||||||
|
|
||||||
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
|
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
|
||||||
preventor.runPreClassLoaderInitiators();
|
preventor.runPreClassLoaderInitiators();
|
||||||
@@ -98,6 +163,10 @@ public final class ClassLoaderLifeCycle implements LifeCycle {
|
|||||||
return classLoader;
|
return classLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ClassLoaderAppendListener {
|
||||||
|
<C extends ClassLoader> C apply(C classLoader);
|
||||||
|
}
|
||||||
|
|
||||||
private class ClassLoaderAndPreventor {
|
private class ClassLoaderAndPreventor {
|
||||||
|
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
|
|||||||
@@ -70,7 +70,12 @@ class ClassLoaderLifeCycleTest {
|
|||||||
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
|
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
|
||||||
|
|
||||||
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
|
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
|
||||||
lifeCycle.setClassLoaderAppendListener(c -> spy(c));
|
lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() {
|
||||||
|
@Override
|
||||||
|
public <C extends ClassLoader> C apply(C classLoader) {
|
||||||
|
return spy(classLoader);
|
||||||
|
}
|
||||||
|
});
|
||||||
lifeCycle.initialize();
|
lifeCycle.initialize();
|
||||||
|
|
||||||
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
||||||
|
|||||||
Reference in New Issue
Block a user