diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index 538a3a578b..e3c42b09f5 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -47,7 +47,9 @@ import java.net.URLConnection; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import javax.annotation.CheckForNull; import jenkins.model.Jenkins; +import jenkins.util.JenkinsJVM; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswordCredentials; @@ -215,8 +217,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl iterator = instance.list.iterator(); iterator.hasNext(); ) { + Filter f = iterator.next(); + try { + f.destroy(); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, "Filter " + f + " propagated an exception from its destroy method", e); + } catch (Error e) { + throw e; // we are not supposed to catch errors, don't log as could be an OOM + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, "Filter " + f + " propagated an exception from its destroy method", e); + } + iterator.remove(); + } + } + } + private static final Logger LOGGER = Logger.getLogger(PluginServletFilter.class.getName()); } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 2d22b3a79f..95603b8324 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -178,6 +178,7 @@ import hudson.util.JenkinsReloadFailed; import hudson.util.Memoizer; import hudson.util.MultipartFormDataParser; import hudson.util.NamingThreadFactory; +import hudson.util.PluginServletFilter; import hudson.util.RemotingDiagnostics; import hudson.util.RemotingDiagnostics.HeapDump; import hudson.util.TextFile; @@ -198,6 +199,7 @@ import jenkins.security.ConfidentialStore; import jenkins.security.SecurityListener; import jenkins.security.MasterToSlaveCallable; import jenkins.slaves.WorkspaceLocator; +import jenkins.util.JenkinsJVM; import jenkins.util.Timer; import jenkins.util.io.FileBoolean; import net.sf.json.JSONObject; @@ -770,6 +772,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer }) protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException { + JenkinsJVMAccess._setJenkinsJVM(true); // set it for unit tests as they will not have gone through WebAppMain long start = System.currentTimeMillis(); // As Jenkins is starting, grant this process full control @@ -2902,6 +2905,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve LOGGER.log(Level.WARNING, "Failed to shut down properly",e); } + PluginServletFilter.cleanUp(); + LogFactory.releaseAll(); theInstance = null; @@ -4354,4 +4359,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } } + private static final class JenkinsJVMAccess extends JenkinsJVM { + private static void _setJenkinsJVM(boolean jenkinsJVM) { + JenkinsJVM.setJenkinsJVM(jenkinsJVM); + } + } + } diff --git a/core/src/main/java/jenkins/util/JenkinsJVM.java b/core/src/main/java/jenkins/util/JenkinsJVM.java new file mode 100644 index 0000000000..dce80915f3 --- /dev/null +++ b/core/src/main/java/jenkins/util/JenkinsJVM.java @@ -0,0 +1,73 @@ +package jenkins.util; + +import hudson.WebAppMain; +import javax.servlet.ServletContextEvent; +import jenkins.model.Jenkins; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * A utility class to identify if the current JVM is the one that is running {@link Jenkins} + * + * @since TODO + */ +public class JenkinsJVM { + /** + * Flag to identify the JVM running {@link Jenkins}. + */ + private static boolean jenkinsJVM; + + /** + * Protect against people instantiating this class. + */ + @Restricted(NoExternalUse.class) + protected JenkinsJVM() { + throw new IllegalAccessError("Utility class"); + } + + /** + * Identify whether the classloader that loaded this class is the classloader from which {@link Jenkins} has been + * started. + * + * @return {@code true} if this is the classloader on the JVM that started {@link Jenkins} otherwise {@code false} + */ + public static boolean isJenkinsJVM() { + return jenkinsJVM; + } + + /** + * Verify that the classloader that loaded this class is the classloader from which {@link Jenkins} has been + * started. + * + * @throws IllegalStateException if this is not the classloader on the JVM that started {@link Jenkins}. + */ + public static void checkJenkinsJVM() { + if (!isJenkinsJVM()) { + throw new IllegalStateException("Not running on the Jenkins master JVM"); + } + } + + /** + * Verify that the classloader that loaded this class is not the classloader from which {@link Jenkins} has been + * started. + * + * @throws IllegalStateException if this is the classloader on the JVM that started {@link Jenkins}. + */ + public static void checkNotJenkinsJVM() { + if (isJenkinsJVM()) { + throw new IllegalStateException("Running on the Jenkins master JVM"); + } + } + + /** + * Used by {@link WebAppMain#contextInitialized(ServletContextEvent)} and + * {@link WebAppMain#contextDestroyed(ServletContextEvent)} to identify the classloader and JVM which started + * {@link Jenkins} + * + * @param jenkinsJVM {@code true} if and only if this is the classloader and JVM that started {@link Jenkins}. + */ + @Restricted(NoExternalUse.class) + protected static void setJenkinsJVM(boolean jenkinsJVM) { + JenkinsJVM.jenkinsJVM = jenkinsJVM; + } +} diff --git a/core/src/test/java/jenkins/util/JenkinsJVMTest.java b/core/src/test/java/jenkins/util/JenkinsJVMTest.java new file mode 100644 index 0000000000..8921deb661 --- /dev/null +++ b/core/src/test/java/jenkins/util/JenkinsJVMTest.java @@ -0,0 +1,36 @@ +package jenkins.util; + +import org.junit.Test; + +public class JenkinsJVMTest { + + @Test + public void checkNotJenkinsJVM_WhenNotInAJenkinsJVM() { + JenkinsJVM.checkNotJenkinsJVM(); + } + + @Test(expected = IllegalStateException.class) + public void checkJenkinsJVM_WhenNotInAJenkinsJVM() { + JenkinsJVM.checkJenkinsJVM(); + } + + @Test(expected = IllegalStateException.class) + public void checkNotJenkinsJVM_WhenInAJenkinsJVM() { + JenkinsJVM.setJenkinsJVM(true); + try { + JenkinsJVM.checkNotJenkinsJVM(); + } finally { + JenkinsJVM.setJenkinsJVM(false); + } + } + + @Test + public void checkJenkinsJVM_WhenInAJenkinsJVM() { + JenkinsJVM.setJenkinsJVM(true); + try { + JenkinsJVM.checkJenkinsJVM(); + } finally { + JenkinsJVM.setJenkinsJVM(false); + } + } +} diff --git a/test/src/test/java/jenkins/util/JenkinsJVMRealTest.java b/test/src/test/java/jenkins/util/JenkinsJVMRealTest.java new file mode 100644 index 0000000000..bdce8a15e3 --- /dev/null +++ b/test/src/test/java/jenkins/util/JenkinsJVMRealTest.java @@ -0,0 +1,34 @@ +package jenkins.util; + +import hudson.slaves.DumbSlave; +import java.io.IOException; +import jenkins.security.MasterToSlaveCallable; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class JenkinsJVMRealTest { + + @ClassRule + public static JenkinsRule j = new JenkinsRule(); + + @Test + public void isJenkinsJVM() throws Exception { + assertThat(new IsJenkinsJVM().call(), is(true)); + DumbSlave slave = j.createOnlineSlave(); + assertThat(slave.getChannel().call(new IsJenkinsJVM()), is(false)); + } + + public static class IsJenkinsJVM extends MasterToSlaveCallable { + + @Override + public Boolean call() throws IOException { + return JenkinsJVM.isJenkinsJVM(); + } + } + +}