diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 2b05a8202e..3bb638e35f 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -23,19 +23,12 @@ */ package hudson; -import static hudson.init.InitMilestone.PLUGINS_PREPARED; -import static hudson.init.InitMilestone.PLUGINS_STARTED; -import static hudson.init.InitMilestone.PLUGINS_LISTED; - -import com.google.inject.Guice; -import com.google.inject.Injector; import hudson.PluginWrapper.Dependency; +import hudson.init.InitMilestone; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; import hudson.model.AbstractModelObject; import hudson.model.Failure; -import jenkins.ClassLoaderReflectionToolkit; -import jenkins.model.Jenkins; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.CyclicGraphDetector; @@ -43,6 +36,9 @@ import hudson.util.CyclicGraphDetector.CycleDetectedException; import hudson.util.FormValidation; import hudson.util.PersistedList; import hudson.util.Service; +import jenkins.ClassLoaderReflectionToolkit; +import jenkins.InitReactorRunner; +import jenkins.model.Jenkins; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; @@ -66,25 +62,26 @@ import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.ListIterator; -import java.util.Set; import java.util.Map; -import java.util.HashMap; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; +import static hudson.init.InitMilestone.*; + /** * Manages {@link PluginWrapper}s. * @@ -329,6 +326,37 @@ public abstract class PluginManager extends AbstractModelObject { }}); } + /** + * TODO: revisit where/how to expose this. This is an experiment. + */ + public void dynamicLoad(File arc) throws Exception { + PluginWrapper p = strategy.createPluginWrapper(arc); + if (getPlugin(p.getShortName())!=null) + throw new IllegalArgumentException("Dynamic reloading isn't possible"); + + // TODO: check cyclic dependency + + activePlugins.add(p); + + try { + p.resolvePluginDependencies(); + strategy.load(p); + + p.getPlugin().postInitialize(); + } catch (Exception e) { + failedPlugins.add(new FailedPlugin(p.getShortName(), e)); + activePlugins.remove(p); + plugins.remove(p); + throw e; + } + + // run initializers + // TODO: need to ignore base types + Reactor r = new Reactor(InitMilestone.ordering()); + r.addAll(new InitializerFinder(p.classLoader).discoverTasks(r)); + new InitReactorRunner().run(r); + } + /** * If the war file has any "/WEB-INF/plugins/*.hpi", extract them into the plugin directory. * diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java new file mode 100644 index 0000000000..e412df10e9 --- /dev/null +++ b/core/src/main/java/jenkins/InitReactorRunner.java @@ -0,0 +1,96 @@ +package jenkins; + +import hudson.init.InitMilestone; +import hudson.init.InitReactorListener; +import hudson.util.DaemonThreadFactory; +import hudson.util.Service; +import jenkins.model.Configuration; +import jenkins.model.Jenkins; +import org.jvnet.hudson.reactor.Milestone; +import org.jvnet.hudson.reactor.Reactor; +import org.jvnet.hudson.reactor.ReactorException; +import org.jvnet.hudson.reactor.ReactorListener; +import org.jvnet.hudson.reactor.Task; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.util.logging.Level.SEVERE; + +/** + * Executes the {@link Reactor} for the purpose of bootup. + * + * @author Kohsuke Kawaguchi + */ +public class InitReactorRunner { + public void run(Reactor reactor) throws InterruptedException, ReactorException, IOException { + reactor.addAll(InitMilestone.ordering().discoverTasks(reactor)); + + ExecutorService es; + if (Jenkins.PARALLEL_LOAD) + es = new ThreadPoolExecutor( + TWICE_CPU_NUM, TWICE_CPU_NUM, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DaemonThreadFactory()); + else + es = Executors.newSingleThreadExecutor(new DaemonThreadFactory()); + try { + reactor.execute(es,buildReactorListener()); + } finally { + es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks + } + + } + + /** + * Aggregates all the listeners into one and returns it. + * + *

+ * At this point plugins are not loaded yet, so we fall back to the META-INF/services look up to discover implementations. + * As such there's no way for plugins to participate into this process. + */ + private ReactorListener buildReactorListener() throws IOException { + List r = (List) Service.loadInstances(Thread.currentThread().getContextClassLoader(), InitReactorListener.class); + r.add(new ReactorListener() { + final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") ); + public void onTaskStarted(Task t) { + LOGGER.log(level,"Started "+t.getDisplayName()); + } + + public void onTaskCompleted(Task t) { + LOGGER.log(level,"Completed "+t.getDisplayName()); + } + + public void onTaskFailed(Task t, Throwable err, boolean fatal) { + LOGGER.log(SEVERE, "Failed "+t.getDisplayName(),err); + } + + public void onAttained(Milestone milestone) { + Level lv = level; + String s = "Attained "+milestone.toString(); + if (milestone instanceof InitMilestone) { + lv = Level.INFO; // noteworthy milestones --- at least while we debug problems further + onInitMilestoneAttained((InitMilestone) milestone); + s = milestone.toString(); + } + LOGGER.log(lv,s); + } + }); + return new ReactorListener.Aggregator(r); + } + + /** + * Called when the init milestone is attained. + */ + protected void onInitMilestoneAttained(InitMilestone milestone) { + } + + private static final int TWICE_CPU_NUM = Runtime.getRuntime().availableProcessors() * 2; + + private static final Logger LOGGER = Logger.getLogger(InitReactorRunner.class.getName()); +} diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index b6e3851d4c..3b1f85bec5 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -188,6 +188,7 @@ import hudson.views.ViewsTabBar; import hudson.widgets.Widget; import jenkins.ExtensionComponentSet; import jenkins.ExtensionRefreshException; +import jenkins.InitReactorRunner; import net.sf.json.JSONObject; import org.acegisecurity.AccessDeniedException; import org.acegisecurity.AcegiSecurityException; @@ -810,55 +811,14 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup(), new DaemonThreadFactory()); - else - es = Executors.newSingleThreadExecutor(new DaemonThreadFactory()); - try { - reactor.execute(es,buildReactorListener()); - } finally { - es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks - } + new InitReactorRunner() { + @Override + protected void onInitMilestoneAttained(InitMilestone milestone) { + initLevel = milestone; + } + }.run(reactor); } - /** - * Aggregates all the listeners into one and returns it. - * - *

- * At this point plugins are not loaded yet, so we fall back to the META-INF/services look up to discover implementations. - * As such there's no way for plugins to participate into this process. - */ - private ReactorListener buildReactorListener() throws IOException { - List r = (List) Service.loadInstances(Thread.currentThread().getContextClassLoader(), InitReactorListener.class); - r.add(new ReactorListener() { - final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") ); - public void onTaskStarted(Task t) { - LOGGER.log(level,"Started "+t.getDisplayName()); - } - - public void onTaskCompleted(Task t) { - LOGGER.log(level,"Completed "+t.getDisplayName()); - } - - public void onTaskFailed(Task t, Throwable err, boolean fatal) { - LOGGER.log(SEVERE, "Failed "+t.getDisplayName(),err); - } - - public void onAttained(Milestone milestone) { - Level lv = level; - String s = "Attained "+milestone.toString(); - if (milestone instanceof InitMilestone) { - lv = Level.INFO; // noteworthy milestones --- at least while we debug problems further - initLevel = (InitMilestone)milestone; - s = initLevel.toString(); - } - LOGGER.log(lv,s); - } - }); - return new ReactorListener.Aggregator(r); - } public TcpSlaveAgentListener getTcpSlaveAgentListener() { return tcpSlaveAgentListener;