From b40c6a556c817511d7540e697d18b090bc390d16 Mon Sep 17 00:00:00 2001 From: jbq Date: Wed, 12 Sep 2007 16:27:53 +0000 Subject: [PATCH] Issue number: 806 Provide synchronous SCM polling in the order of dependencies. For now, to activate, edit hudson.triggers.SCMTrigger.xml and add true This change also relaxes visibility of Trigger.run() from protected to public. TODO: connect the UI and provide a global crontab spec setting. This will probably happen when I get some feedback on this change. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@4700 71c3de6d-444a-0410-be80-ed276b4c234a --- core/.cvsignore | 1 + .../main/java/hudson/DependencyRunner.java | 55 +++++++++++++++++++ .../main/java/hudson/triggers/SCMTrigger.java | 22 ++++++-- .../java/hudson/triggers/TimerTrigger.java | 12 ++-- .../main/java/hudson/triggers/Trigger.java | 48 ++++++++++++---- 5 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/hudson/DependencyRunner.java diff --git a/core/.cvsignore b/core/.cvsignore index f87512f564..be22279ea1 100644 --- a/core/.cvsignore +++ b/core/.cvsignore @@ -10,3 +10,4 @@ target hudson.iml hudson-core.iml hudson.ipr +.settings diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java new file mode 100644 index 0000000000..8093fc617c --- /dev/null +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -0,0 +1,55 @@ +package hudson; + +import hudson.model.AbstractProject; +import hudson.model.Hudson; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Runs a job on all projects in the order of dependencies + */ +public class DependencyRunner implements Runnable { + AbstractProject currentProject; + + ProjectRunnable runnable; + + List polledProjects = new ArrayList(); + + @SuppressWarnings("unchecked") + public DependencyRunner(ProjectRunnable runnable) { + this.runnable = runnable; + } + + public void run() { + Set topLevelProjects = new HashSet(); + // Get all top-level projects + for (AbstractProject p : Hudson.getInstance().getAllItems( + AbstractProject.class)) + if (p.getUpstreamProjects().size() == 0) + topLevelProjects.add(p); + populate(topLevelProjects); + for (AbstractProject p : polledProjects) + runnable.run(p); + } + + private void populate(Set projectList) { + for (AbstractProject p : projectList) { + if (polledProjects.contains(p)) + // Project will be readded at the queue, so that we always use + // the longest path + polledProjects.remove(p); + + polledProjects.add(p); + + // Add all downstream dependencies + populate(new HashSet(p.getDownstreamProjects())); + } + } + + public interface ProjectRunnable { + void run(AbstractProject p); + } +} diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 1cd8d666ef..3e9e4e9e5c 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -4,6 +4,7 @@ import antlr.ANTLRException; import hudson.Util; import hudson.model.AbstractProject; import hudson.model.Action; +import hudson.model.Hudson; import hudson.model.Item; import hudson.model.Project; import hudson.model.SCMedItem; @@ -63,15 +64,20 @@ public class SCMTrigger extends Trigger { return super.readResolve(); } - protected void run() { + public void run() { if(pollingScheduled) return; // noop pollingScheduled = true; - // schedule the polling. - // even if we end up submitting this too many times, that's OK. - // the real exclusion control happens inside Runner. - DESCRIPTOR.getExecutor().submit(new Runner()); + if (DESCRIPTOR.synchronousPolling) { + // Run the trigger directly without threading, as it's already taken care of by Trigger.Cron + new Runner().run(); + } else { + // schedule the polling. + // even if we end up submitting this too many times, that's OK. + // the real exclusion control happens inside Runner. + DESCRIPTOR.getExecutor().submit(new Runner()); + } } public Action getProjectAction() { @@ -97,6 +103,12 @@ public class SCMTrigger extends Trigger { */ transient volatile ExecutorService executor; + /** + * Whether the projects should be polled all in one go in the order of dependencies. The default behavior is + * that each project polls for changes independently. + */ + public boolean synchronousPolling = false; + /** * Jobs that are being polled. The value is useful for trouble-shooting. */ diff --git a/core/src/main/java/hudson/triggers/TimerTrigger.java b/core/src/main/java/hudson/triggers/TimerTrigger.java index 657da3609b..66191db256 100644 --- a/core/src/main/java/hudson/triggers/TimerTrigger.java +++ b/core/src/main/java/hudson/triggers/TimerTrigger.java @@ -1,17 +1,19 @@ package hudson.triggers; -import antlr.ANTLRException; import static hudson.Util.fixNull; -import hudson.model.Descriptor; import hudson.model.BuildableItem; import hudson.model.Item; import hudson.scheduler.CronTabList; import hudson.util.FormFieldValidator; + +import java.io.IOException; + +import javax.servlet.ServletException; + import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; -import javax.servlet.ServletException; -import java.io.IOException; +import antlr.ANTLRException; /** * {@link Trigger} that runs a job periodically. @@ -23,7 +25,7 @@ public class TimerTrigger extends Trigger { super(cronTabSpec); } - protected void run() { + public void run() { job.scheduleBuild(); } diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java index 50defdde7c..849cabc4e4 100644 --- a/core/src/main/java/hudson/triggers/Trigger.java +++ b/core/src/main/java/hudson/triggers/Trigger.java @@ -1,7 +1,9 @@ package hudson.triggers; import antlr.ANTLRException; +import hudson.DependencyRunner; import hudson.ExtensionPoint; +import hudson.DependencyRunner.ProjectRunnable; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Build; @@ -17,6 +19,7 @@ import hudson.scheduler.CronTabList; import java.io.InvalidObjectException; import java.io.ObjectStreamException; import java.util.Calendar; +import java.util.Collection; import java.util.Collections; import java.util.GregorianCalendar; import java.util.Timer; @@ -53,7 +56,7 @@ public abstract class Trigger implements Describable> * This method is invoked when {@link #Trigger(String)} is used * to create an instance, and the crontab matches the current time. */ - protected void run() {} + public void run() {} /** * Called before a {@link Trigger} is removed. @@ -131,16 +134,7 @@ public abstract class Trigger implements Describable> LOGGER.fine("cron checking "+cal.getTime().toLocaleString()); try { - Hudson inst = Hudson.getInstance(); - for (AbstractProject p : inst.getAllItems(AbstractProject.class)) { - for (Trigger t : p.getTriggers().values()) { - LOGGER.fine("cron checking "+p.getName()); - if(t.tabs.check(cal)) { - LOGGER.fine("cron triggered "+p.getName()); - t.run(); - } - } - } + checkTriggers(cal); } catch (Throwable e) { LOGGER.log(Level.WARNING,"Cron thread throw an exception",e); // bug in the code. Don't let the thread die. @@ -151,6 +145,38 @@ public abstract class Trigger implements Describable> } } + public static void checkTriggers(final Calendar cal) { + Hudson inst = Hudson.getInstance(); + + if (SCMTrigger.DESCRIPTOR.synchronousPolling) { + // Process SCMTriggers in the order of dependencies. Note that the crontab spec expressed per-project is + // ignored, only the global setting is honored + // FIXME allow to set a global crontab spec + SCMTrigger.DESCRIPTOR.getExecutor().submit(new DependencyRunner(new ProjectRunnable() { + public void run(AbstractProject p) { + for (Trigger t : (Collection) p.getTriggers().values()) { + if (t instanceof SCMTrigger) + t.run(); + } + } + })); + } + + // Process all triggers, except SCMTriggers when synchronousPolling is set + for (AbstractProject p : inst.getAllItems(AbstractProject.class)) { + for (Trigger t : p.getTriggers().values()) { + if (! (t instanceof SCMTrigger && SCMTrigger.DESCRIPTOR.synchronousPolling)) { + LOGGER.fine("cron checking "+p.getName()); + + if (t.tabs.check(cal)) { + LOGGER.fine("cron triggered "+p.getName()); + t.run(); + } + } + } + } + } + private static final Logger LOGGER = Logger.getLogger(Trigger.class.getName()); /**