diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa519112bd..9a4de014bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Support for Java versions > 8
+- Simple ClassLoaderLifeCycle to fix integration tests on Java > 8
### Changed
- Upgrade [Legman](https://github.com/sdorra/legman) to v1.6.2 in order to fix execution on Java versions > 8
diff --git a/scm-it/pom.xml b/scm-it/pom.xml
index c38c73ce3e..9064a39fc1 100644
--- a/scm-it/pom.xml
+++ b/scm-it/pom.xml
@@ -200,6 +200,10 @@
java.awt.headless
true
+
+ sonia.scm.classloading.lifecycle
+ simple
+
/scm
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 487eb0bfeb..924e176024 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -720,6 +720,10 @@
scm.stage
${scm.stage}
+
+ sonia.scm.classloading.lifecycle
+ simple
+
${project.basedir}/src/main/conf/jetty.xml
0
@@ -805,6 +809,10 @@
scm.home
target/scm-it
+
+ sonia.scm.classloading.lifecycle
+ simple
+
${project.basedir}/src/main/conf/jetty.xml
0
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
index a64ed6fa43..fb7d991c1e 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
@@ -3,198 +3,75 @@ package sonia.scm.lifecycle.classloading;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
-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.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 sonia.scm.lifecycle.LifeCycle;
import sonia.scm.plugin.ChildFirstPluginClassLoader;
import sonia.scm.plugin.DefaultPluginClassLoader;
-import java.io.Closeable;
-import java.io.IOException;
import java.net.URL;
-import java.util.ArrayDeque;
-import java.util.Deque;
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.
+ * Base class for ClassLoader LifeCycle implementation in SCM-Manager.
*/
-public final class ClassLoaderLifeCycle implements LifeCycle {
+public abstract class ClassLoaderLifeCycle implements LifeCycle {
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
- private Deque classLoaders = new ArrayDeque<>();
+ @VisibleForTesting
+ static final String PROPERTY = "sonia.scm.classloading.lifecycle";
+
+ public static ClassLoaderLifeCycle create() {
+ ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
+ String implementation = System.getProperty(PROPERTY);
+ if (SimpleClassLoaderLifeCycle.NAME.equalsIgnoreCase(implementation)) {
+ LOG.info("create new simple ClassLoaderLifeCycle");
+ return new SimpleClassLoaderLifeCycle(webappClassLoader);
+ }
+ LOG.info("create new ClassLoaderLifeCycle with leak prevention");
+ return new ClassLoaderLifeCycleWithLeakPrevention(webappClassLoader);
+ }
- private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
private final ClassLoader webappClassLoader;
private BootstrapClassLoader bootstrapClassLoader;
- private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
- @Override
- public C apply(C classLoader) {
- return classLoader;
- }
- };
-
- @VisibleForTesting
- public static ClassLoaderLifeCycle create() {
- ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
- ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader);
- return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory);
- }
-
- ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
- this.classLoaderLeakPreventorFactory = classLoaderLeakPreventorFactory;
- 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;
+ ClassLoaderLifeCycle(ClassLoader webappClassLoader) {
+ this.webappClassLoader = webappClassLoader;
}
+ @Override
public void initialize() {
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
}
- @VisibleForTesting
- void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
- this.classLoaderAppendListener = classLoaderAppendListener;
- }
+ protected abstract T initAndAppend(T classLoader);
public ClassLoader getBootstrapClassLoader() {
checkState(bootstrapClassLoader != null, "%s was not initialized", ClassLoaderLifeCycle.class.getName());
return bootstrapClassLoader;
}
- public ClassLoader createPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
- LOG.debug("create new PluginClassLoader for {}", plugin);
- DefaultPluginClassLoader pluginClassLoader = new DefaultPluginClassLoader(urls, parent, plugin);
- return initAndAppend(pluginClassLoader);
- }
-
public ClassLoader createChildFirstPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
LOG.debug("create new ChildFirstPluginClassLoader for {}", plugin);
ChildFirstPluginClassLoader pluginClassLoader = new ChildFirstPluginClassLoader(urls, parent, plugin);
return initAndAppend(pluginClassLoader);
}
+ public ClassLoader createPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
+ LOG.debug("create new PluginClassLoader for {}", plugin);
+ DefaultPluginClassLoader pluginClassLoader = new DefaultPluginClassLoader(urls, parent, plugin);
+ return initAndAppend(pluginClassLoader);
+ }
+
+ @Override
public void shutdown() {
LOG.info("shutdown classloader infrastructure");
- ClassLoaderAndPreventor clap = classLoaders.poll();
- while (clap != null) {
- clap.shutdown();
- clap = classLoaders.poll();
- }
- // be sure it is realy empty
- classLoaders.clear();
- classLoaders = new ArrayDeque<>();
+ shutdownClassLoaders();
bootstrapClassLoader.markAsShutdown();
bootstrapClassLoader = null;
}
- private T initAndAppend(T originalClassLoader) {
- LOG.debug("init classloader {}", originalClassLoader);
- T classLoader = classLoaderAppendListener.apply(originalClassLoader);
-
- ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
- preventor.runPreClassLoaderInitiators();
- classLoaders.push(new ClassLoaderAndPreventor(classLoader, preventor));
-
- return classLoader;
- }
-
- interface ClassLoaderAppendListener {
- C apply(C classLoader);
- }
-
- private class ClassLoaderAndPreventor {
-
- private final ClassLoader classLoader;
- private final ClassLoaderLeakPreventor preventor;
-
- private ClassLoaderAndPreventor(ClassLoader classLoader, ClassLoaderLeakPreventor preventor) {
- this.classLoader = classLoader;
- this.preventor = preventor;
- }
-
- void shutdown() {
- LOG.debug("shutdown classloader {}", classLoader);
- preventor.runCleanUps();
-
- if (classLoader != webappClassLoader) {
- close();
- }
- }
-
- private void close() {
- if (classLoader instanceof Closeable) {
- LOG.trace("close classloader {}", classLoader);
- try {
- ((Closeable) classLoader).close();
- } catch (IOException e) {
- LOG.warn("failed to close classloader", e);
- }
- }
- }
- }
+ protected abstract void shutdownClassLoaders();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java
new file mode 100644
index 0000000000..e2e7a032b7
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java
@@ -0,0 +1,163 @@
+package sonia.scm.lifecycle.classloading;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
+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.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 java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
+
+/**
+ * Creates and shutdown SCM-Manager ClassLoaders with ClassLoader leak detection.
+ */
+final class ClassLoaderLifeCycleWithLeakPrevention extends ClassLoaderLifeCycle {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycleWithLeakPrevention.class);
+
+ private Deque classLoaders = new ArrayDeque<>();
+
+ private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
+
+ private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
+ @Override
+ public C apply(C classLoader) {
+ return classLoader;
+ }
+ };
+
+ ClassLoaderLifeCycleWithLeakPrevention(ClassLoader webappClassLoader) {
+ this(webappClassLoader, createClassLoaderLeakPreventorFactory(webappClassLoader));
+ }
+
+ ClassLoaderLifeCycleWithLeakPrevention(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
+ super(webappClassLoader);
+ this.classLoaderLeakPreventorFactory = classLoaderLeakPreventorFactory;
+ }
+
+ 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{}):", ClassLoaderLifeCycleWithLeakPrevention.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;
+ }
+
+ @VisibleForTesting
+ void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
+ this.classLoaderAppendListener = classLoaderAppendListener;
+ }
+
+ @Override
+ protected void shutdownClassLoaders() {
+ ClassLoaderAndPreventor clap = classLoaders.poll();
+ while (clap != null) {
+ clap.shutdown();
+ clap = classLoaders.poll();
+ }
+ // be sure it is realy empty
+ classLoaders.clear();
+ classLoaders = new ArrayDeque<>();
+ }
+
+ @Override
+ protected T initAndAppend(T originalClassLoader) {
+ LOG.debug("init classloader {}", originalClassLoader);
+ T classLoader = classLoaderAppendListener.apply(originalClassLoader);
+
+ ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
+ preventor.runPreClassLoaderInitiators();
+ classLoaders.push(new ClassLoaderAndPreventor(classLoader, preventor));
+
+ return classLoader;
+ }
+
+ interface ClassLoaderAppendListener {
+ C apply(C classLoader);
+ }
+
+ private static class ClassLoaderAndPreventor {
+
+ private final ClassLoader classLoader;
+ private final ClassLoaderLeakPreventor preventor;
+
+ private ClassLoaderAndPreventor(ClassLoader classLoader, ClassLoaderLeakPreventor preventor) {
+ this.classLoader = classLoader;
+ this.preventor = preventor;
+ }
+
+ void shutdown() {
+ LOG.debug("shutdown classloader {}", classLoader);
+ preventor.runCleanUps();
+ close();
+ }
+
+ private void close() {
+ if (classLoader instanceof Closeable) {
+ LOG.trace("close classloader {}", classLoader);
+ try {
+ ((Closeable) classLoader).close();
+ } catch (IOException e) {
+ LOG.warn("failed to close classloader", e);
+ }
+ }
+ }
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java
new file mode 100644
index 0000000000..3c18c10680
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java
@@ -0,0 +1,56 @@
+package sonia.scm.lifecycle.classloading;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Creates and shutdown SCM-Manager ClassLoaders with ClassLoader leak detection.
+ */
+class SimpleClassLoaderLifeCycle extends ClassLoaderLifeCycle {
+
+ static final String NAME = "simple";
+
+ private static final Logger LOG = LoggerFactory.getLogger(SimpleClassLoaderLifeCycle.class);
+
+ private Deque classLoaders = new ArrayDeque<>();
+
+ SimpleClassLoaderLifeCycle(ClassLoader webappClassLoader) {
+ super(webappClassLoader);
+ }
+
+ @Override
+ protected T initAndAppend(T classLoader) {
+ LOG.debug("init classloader {}", classLoader);
+ classLoaders.push(classLoader);
+ return classLoader;
+ }
+
+ @Override
+ protected void shutdownClassLoaders() {
+ ClassLoader classLoader = classLoaders.poll();
+ while (classLoader != null) {
+ shutdown(classLoader);
+ classLoader = classLoaders.poll();
+ }
+ // be sure it is realy empty
+ classLoaders.clear();
+ classLoaders = new ArrayDeque<>();
+ }
+
+ private void shutdown(ClassLoader classLoader) {
+ LOG.debug("shutdown classloader {}", classLoader);
+ if (classLoader instanceof Closeable) {
+ LOG.trace("close classloader {}", classLoader);
+ try {
+ ((Closeable) classLoader).close();
+ } catch (IOException e) {
+ LOG.warn("failed to close classloader", e);
+ }
+ }
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
index a8f37777d7..619b09940e 100644
--- a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
@@ -1,116 +1,25 @@
package sonia.scm.lifecycle.classloading;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
-import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-@ExtendWith(MockitoExtension.class)
class ClassLoaderLifeCycleTest {
- @Mock
- private ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
-
- @Mock
- private ClassLoaderLeakPreventor classLoaderLeakPreventor;
-
@Test
- void shouldThrowIllegalStateExceptionWithoutInit() {
- ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
- assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
+ void shouldCreateSimpleClassLoader() {
+ System.setProperty(ClassLoaderLifeCycle.PROPERTY, SimpleClassLoaderLifeCycle.NAME);
+ try {
+ ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
+ assertThat(classLoaderLifeCycle).isInstanceOf(SimpleClassLoaderLifeCycle.class);
+ } finally {
+ System.clearProperty(ClassLoaderLifeCycle.PROPERTY);
+ }
}
@Test
- void shouldThrowIllegalStateExceptionAfterShutdown() {
- ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
- lifeCycle.initialize();
-
- lifeCycle.shutdown();
- assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
+ void shouldCreateDefaultClassLoader() {
+ ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
+ assertThat(classLoaderLifeCycle).isInstanceOf(ClassLoaderLifeCycleWithLeakPrevention.class);
}
-
- @Test
- void shouldCreateBootstrapClassLoaderOnInit() {
- ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
- lifeCycle.initialize();
-
- assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull();
- }
-
- @Test
- void shouldCallTheLeakPreventor() {
- ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
-
- lifeCycle.initialize();
- verify(classLoaderLeakPreventor, times(2)).runPreClassLoaderInitiators();
-
- lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
- lifeCycle.createPluginClassLoader(new URL[0], null, "b");
- verify(classLoaderLeakPreventor, times(4)).runPreClassLoaderInitiators();
-
- lifeCycle.shutdown();
- verify(classLoaderLeakPreventor, times(4)).runCleanUps();
- }
-
- @Test
- void shouldCloseCloseableClassLoaders() throws IOException {
- // we use URLClassLoader, because we must be sure that the classloader is closable
- URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
-
- ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
- lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() {
- @Override
- public C apply(C classLoader) {
- return spy(classLoader);
- }
- });
- lifeCycle.initialize();
-
- ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
- ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b");
-
- lifeCycle.shutdown();
-
- closed(pluginB);
- closed(pluginA);
-
- neverClosed(webappClassLoader);
- }
-
- private void neverClosed(Object object) throws IOException {
- Closeable closeable = closeable(object);
- verify(closeable, never()).close();
- }
-
- private void closed(Object object) throws IOException {
- Closeable closeable = closeable(object);
- verify(closeable).close();
- }
-
- private Closeable closeable(Object object) {
- assertThat(object).isInstanceOf(Closeable.class);
- return (Closeable) object;
- }
-
- private ClassLoaderLifeCycle createMockedLifeCycle() {
- return createMockedLifeCycle(Thread.currentThread().getContextClassLoader());
- }
-
- private ClassLoaderLifeCycle createMockedLifeCycle(ClassLoader classLoader) {
- when(classLoaderLeakPreventorFactory.newLeakPreventor(any(ClassLoader.class))).thenReturn(classLoaderLeakPreventor);
- return new ClassLoaderLifeCycle(classLoader, classLoaderLeakPreventorFactory);
- }
-
}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java
new file mode 100644
index 0000000000..9c76fbfd00
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java
@@ -0,0 +1,116 @@
+package sonia.scm.lifecycle.classloading;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
+import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class ClassLoaderLifeCycleWithLeakPreventionTest {
+
+ @Mock
+ private ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
+
+ @Mock
+ private ClassLoaderLeakPreventor classLoaderLeakPreventor;
+
+ @Test
+ void shouldThrowIllegalStateExceptionWithoutInit() {
+ ClassLoaderLifeCycleWithLeakPrevention lifeCycle = new ClassLoaderLifeCycleWithLeakPrevention(Thread.currentThread().getContextClassLoader());
+ assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
+ }
+
+ @Test
+ void shouldThrowIllegalStateExceptionAfterShutdown() {
+ ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle();
+ lifeCycle.initialize();
+
+ lifeCycle.shutdown();
+ assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
+ }
+
+ @Test
+ void shouldCreateBootstrapClassLoaderOnInit() {
+ ClassLoaderLifeCycleWithLeakPrevention lifeCycle = new ClassLoaderLifeCycleWithLeakPrevention(Thread.currentThread().getContextClassLoader());
+ lifeCycle.initialize();
+
+ assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull();
+ }
+
+ @Test
+ void shouldCallTheLeakPreventor() {
+ ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle();
+
+ lifeCycle.initialize();
+ verify(classLoaderLeakPreventor, times(1)).runPreClassLoaderInitiators();
+
+ lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
+ lifeCycle.createPluginClassLoader(new URL[0], null, "b");
+ verify(classLoaderLeakPreventor, times(3)).runPreClassLoaderInitiators();
+
+ lifeCycle.shutdown();
+ verify(classLoaderLeakPreventor, times(3)).runCleanUps();
+ }
+
+ @Test
+ void shouldCloseCloseableClassLoaders() throws IOException {
+ // we use URLClassLoader, because we must be sure that the classloader is closable
+ URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
+
+ ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle(webappClassLoader);
+ lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycleWithLeakPrevention.ClassLoaderAppendListener() {
+ @Override
+ public C apply(C classLoader) {
+ return spy(classLoader);
+ }
+ });
+ lifeCycle.initialize();
+
+ ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
+ ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b");
+
+ lifeCycle.shutdown();
+
+ closed(pluginB);
+ closed(pluginA);
+
+ neverClosed(webappClassLoader);
+ }
+
+ private void neverClosed(Object object) throws IOException {
+ Closeable closeable = closeable(object);
+ verify(closeable, never()).close();
+ }
+
+ private void closed(Object object) throws IOException {
+ Closeable closeable = closeable(object);
+ verify(closeable).close();
+ }
+
+ private Closeable closeable(Object object) {
+ assertThat(object).isInstanceOf(Closeable.class);
+ return (Closeable) object;
+ }
+
+ private ClassLoaderLifeCycleWithLeakPrevention createMockedLifeCycle() {
+ return createMockedLifeCycle(Thread.currentThread().getContextClassLoader());
+ }
+
+ private ClassLoaderLifeCycleWithLeakPrevention createMockedLifeCycle(ClassLoader classLoader) {
+ when(classLoaderLeakPreventorFactory.newLeakPreventor(any(ClassLoader.class))).thenReturn(classLoaderLeakPreventor);
+ return new ClassLoaderLifeCycleWithLeakPrevention(classLoader, classLoaderLeakPreventorFactory);
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java
new file mode 100644
index 0000000000..a40d0fb353
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java
@@ -0,0 +1,37 @@
+package sonia.scm.lifecycle.classloading;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.Closeable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class SimpleClassLoaderLifeCycleTest {
+
+ @Test
+ void shouldCloseClosableClassLoaderOnShutdown() {
+ SimpleClassLoaderLifeCycle lifeCycle = new SimpleClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader());
+ lifeCycle.initialize();
+
+ ClosableClassLoader classLoader = new ClosableClassLoader();
+ lifeCycle.initAndAppend(classLoader);
+
+ lifeCycle.shutdown();
+
+ assertThat(classLoader.closed).isTrue();
+ }
+
+ private static class ClosableClassLoader extends ClassLoader implements Closeable {
+
+ private boolean closed = false;
+
+ public ClosableClassLoader() {
+ super();
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ }
+}