diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java index 12b407a8ff..5ac1002677 100644 --- a/core/src/main/java/hudson/WebAppMain.java +++ b/core/src/main/java/hudson/WebAppMain.java @@ -25,6 +25,7 @@ package hudson; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; import com.thoughtworks.xstream.core.JVM; +import com.trilead.ssh2.util.IOUtils; import hudson.model.Hudson; import hudson.util.BootFailure; import jenkins.model.Jenkins; @@ -53,15 +54,19 @@ import javax.servlet.ServletResponse; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; +import java.util.Date; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import java.security.Security; import java.util.logging.LogRecord; +import static java.util.logging.Level.*; + /** * Entry point when Hudson is used as a webapp. * @@ -84,6 +89,7 @@ public final class WebAppMain implements ServletContextListener { */ public void contextInitialized(ServletContextEvent event) { final ServletContext context = event.getServletContext(); + File home=null; try { // use the current request to determine the language @@ -111,7 +117,7 @@ public final class WebAppMain implements ServletContextListener { installLogger(); final FileAndDescription describedHomeDir = getHomeDir(event); - final File home = describedHomeDir.file.getAbsoluteFile(); + home = describedHomeDir.file.getAbsoluteFile(); home.mkdirs(); System.out.println("Jenkins home directory: "+home+" found at: "+describedHomeDir.description); @@ -119,6 +125,8 @@ public final class WebAppMain implements ServletContextListener { if (!home.exists()) throw new NoHomeDir(home); + recordBootAttempt(home); + // make sure that we are using XStream in the "enhanced" (JVM-specific) mode if(jvm.bestReflectionProvider().getClass()==PureJavaReflectionProvider.class) { throw new IncompatibleVMDetected(); // nope @@ -191,13 +199,13 @@ public final class WebAppMain implements ServletContextListener { // if this works we are all happy } catch (TransformerFactoryConfigurationError x) { // no it didn't. - LOGGER.log(Level.WARNING, "XSLT not configured correctly. Hudson will try to fix this. See http://issues.apache.org/bugzilla/show_bug.cgi?id=40895 for more details",x); + LOGGER.log(WARNING, "XSLT not configured correctly. Hudson will try to fix this. See http://issues.apache.org/bugzilla/show_bug.cgi?id=40895 for more details",x); System.setProperty(TransformerFactory.class.getName(),"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); try { TransformerFactory.newInstance(); LOGGER.info("XSLT is set to the JAXP RI in JRE"); } catch(TransformerFactoryConfigurationError y) { - LOGGER.log(Level.SEVERE, "Failed to correct the problem."); + LOGGER.log(SEVERE, "Failed to correct the problem."); } } @@ -205,22 +213,25 @@ public final class WebAppMain implements ServletContextListener { context.setAttribute(APP,new HudsonIsLoading()); + final File _home = home; initThread = new Thread("Jenkins initialization thread") { @Override public void run() { boolean success = false; try { - Jenkins instance = new Hudson(home, context); + Jenkins instance = new Hudson(_home, context); context.setAttribute(APP, instance); + BootFailure.getBootFailureFile(_home).delete(); + // at this point we are open for business and serving requests normally LOGGER.info("Jenkins is fully up and running"); success = true; } catch (Error e) { - new HudsonFailedToLoad(e).publish(context); + new HudsonFailedToLoad(e).publish(context,_home); throw e; } catch (Exception e) { - new HudsonFailedToLoad(e).publish(context); + new HudsonFailedToLoad(e).publish(context,_home); } finally { Jenkins instance = Jenkins.getInstance(); if(!success && instance!=null) @@ -230,16 +241,34 @@ public final class WebAppMain implements ServletContextListener { }; initThread.start(); } catch (BootFailure e) { - e.publish(context); + e.publish(context,home); } catch (Error e) { - LOGGER.log(Level.SEVERE, "Failed to initialize Jenkins",e); + LOGGER.log(SEVERE, "Failed to initialize Jenkins",e); throw e; } catch (RuntimeException e) { - LOGGER.log(Level.SEVERE, "Failed to initialize Jenkins",e); + LOGGER.log(SEVERE, "Failed to initialize Jenkins",e); throw e; } } + /** + * To assist boot failure script, record the number of boot attempts. + * This file gets deleted in case of successful boot. + * + * @see BootFailure + */ + private void recordBootAttempt(File home) { + FileOutputStream o=null; + try { + o = new FileOutputStream(BootFailure.getBootFailureFile(home), true); + o.write((new Date().toString() + System.getProperty("line.separator", "\n")).toString().getBytes()); + } catch (IOException e) { + LOGGER.log(WARNING, "Failed to record boot attempts",e); + } finally { + IOUtils.closeQuietly(o); + } + } + public static void installExpressionFactory(ServletContextEvent event) { JellyFacet.setExpressionFactory(event, new ExpressionFactory2()); } diff --git a/core/src/main/java/hudson/util/BootFailure.java b/core/src/main/java/hudson/util/BootFailure.java index 78f3ba41a3..bb1671447b 100644 --- a/core/src/main/java/hudson/util/BootFailure.java +++ b/core/src/main/java/hudson/util/BootFailure.java @@ -1,9 +1,18 @@ package hudson.util; +import hudson.WebAppMain; import jenkins.util.groovy.GroovyHookScript; import org.kohsuke.stapler.WebApp; +import javax.annotation.CheckForNull; import javax.servlet.ServletContext; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -11,6 +20,7 @@ import java.util.logging.Logger; * Indicates a fatal boot problem, among {@link ErrorObject} * * @author Kohsuke Kawaguchi + * @see WebAppMain#recordBootAttempt(File) */ public abstract class BootFailure extends ErrorObject { protected BootFailure() { @@ -22,15 +32,57 @@ public abstract class BootFailure extends ErrorObject { /** * Exposes this failure to UI and invoke the hook. + * + * @param home + * JENKINS_HOME if it's already known. */ - public void publish(ServletContext context) { + public void publish(ServletContext context, @CheckForNull File home) { LOGGER.log(Level.SEVERE, "Failed to initialize Jenkins",this); WebApp.get(context).setApp(this); new GroovyHookScript("boot-failure") .bind("exception",this) + .bind("home",home) + .bind("servletContext", context) + .bind("attempts",loadAttempts(home)) .run(); } + /** + * Parses the boot attempt file carefully so as not to cause the entire hook script to fail to execute. + */ + protected List loadAttempts(File home) { + List dates = new ArrayList(); + if (home!=null) { + File f = getBootFailureFile(home); + try { + if (f.exists()) { + BufferedReader r = new BufferedReader(new FileReader(f)); + String line; + while ((line=r.readLine())!=null) { + try { + dates.add(new Date(line)); + } catch (Exception e) { + // ignore any parse error + } + } + } + } catch (IOException e) { + LOGGER.log(Level.WARNING,"Failed to parse "+f,e); + } + } + return dates; + } + private static final Logger LOGGER = Logger.getLogger(BootFailure.class.getName()); + + /** + * This file captures failed boot attempts. + * Every time we try to boot, we add the timestamp to this file, + * then when we boot, the file gets deleted. + */ + public static File getBootFailureFile(File home) { + return new File(home, "failed-boot-attempts.txt"); + } + } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index aa1ebab66a..ecc8317398 100755 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -3086,7 +3086,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro reload(); } catch (Exception e) { LOGGER.log(SEVERE,"Failed to reload Jenkins config",e); - new JenkinsReloadFailed(e).publish(servletContext); + new JenkinsReloadFailed(e).publish(servletContext,root); } } }.start();