package hudson.triggers; import antlr.ANTLRException; import hudson.DependencyRunner; import hudson.DependencyRunner.ProjectRunnable; import hudson.ExtensionPoint; import hudson.slaves.ComputerRetentionWork; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Build; import hudson.model.ComputerSet; import hudson.model.Describable; import hudson.model.FingerprintCleanupThread; import hudson.model.Hudson; import hudson.model.Item; import hudson.model.Project; import hudson.model.WorkspaceCleanupThread; import hudson.scheduler.CronTab; import hudson.scheduler.CronTabList; import hudson.util.DoubleLaunchChecker; 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; import java.util.Date; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; /** * Triggers a {@link Build}. * *

* To register a custom {@link Trigger} from a plugin, * add it to {@link Triggers#TRIGGERS}. * * @author Kohsuke Kawaguchi */ public abstract class Trigger implements Describable>, ExtensionPoint { /** * Called when a {@link Trigger} is loaded into memory and started. * * @param project * given so that the persisted form of this object won't have to have a back pointer. * @param newInstance * True if this is a newly created trigger first attached to the {@link Project}. * False if this is invoked for a {@link Project} loaded from disk. */ public void start(J project, boolean newInstance) { this.job = project; } /** * Executes the triggered task. * * This method is invoked when {@link #Trigger(String)} is used * to create an instance, and the crontab matches the current time. */ public void run() {} /** * Called before a {@link Trigger} is removed. * Under some circumstances, this may be invoked more than once for * a given {@link Trigger}, so be prepared for that. * *

* When the configuration is changed for a project, all triggers * are removed once and then added back. */ public void stop() {} /** * Returns an action object if this {@link Trigger} has an action * to contribute to a {@link Project}. */ public Action getProjectAction() { return null; } public abstract TriggerDescriptor getDescriptor(); protected final String spec; protected transient CronTabList tabs; protected transient J job; /** * Creates a new {@link Trigger} that gets {@link #run() run} * periodically. This is useful when your trigger does * some polling work. */ protected Trigger(String cronTabSpec) throws ANTLRException { this.spec = cronTabSpec; this.tabs = CronTabList.create(cronTabSpec); } /** * Creates a new {@link Trigger} without using cron. */ protected Trigger() { this.spec = ""; this.tabs = new CronTabList(Collections.emptyList()); } /** * Gets the crontab specification. * * If you are not using cron service, just ignore it. */ public final String getSpec() { return spec; } protected Object readResolve() throws ObjectStreamException { try { tabs = CronTabList.create(spec); } catch (ANTLRException e) { InvalidObjectException x = new InvalidObjectException(e.getMessage()); x.initCause(e); throw x; } return this; } /** * Runs every minute to check {@link TimerTrigger} and schedules build. */ private static class Cron extends SafeTimerTask { private final Calendar cal = new GregorianCalendar(); public void doRun() { while(new Date().getTime()-cal.getTimeInMillis()>1000) { LOGGER.fine("cron checking "+cal.getTime().toLocaleString()); try { 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. e.printStackTrace(); } cal.add(Calendar.MINUTE,1); } } } private static Future previousSynchronousPolling; public static void checkTriggers(final Calendar cal) { Hudson inst = Hudson.getInstance(); // Are we using synchronous polling? if (SCMTrigger.DESCRIPTOR.synchronousPolling) { LOGGER.fine("using synchronous polling"); // Check that previous synchronous polling job is done to prevent piling up too many jobs if (previousSynchronousPolling == null || previousSynchronousPolling.isDone()) { // Process SCMTriggers in the order of dependencies. Note that the crontab spec expressed per-project is // ignored, only the global setting is honored. The polling job is submitted only if the previous job has // terminated. // FIXME allow to set a global crontab spec previousSynchronousPolling = SCMTrigger.DESCRIPTOR.getExecutor().submit(new DependencyRunner(new ProjectRunnable() { public void run(AbstractProject p) { for (Trigger t : (Collection) p.getTriggers().values()) { if (t instanceof SCMTrigger) { LOGGER.fine("synchronously triggering SCMTrigger for project " + t.job.getName()); t.run(); } } } })); } else { LOGGER.fine("synchronous polling has detected unfinished jobs, will not trigger additional jobs."); } } // 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.config("cron triggered "+p.getName()); t.run(); } } } } } private static final Logger LOGGER = Logger.getLogger(Trigger.class.getName()); /** * This timer is available for all the components inside Hudson to schedule * some work. */ public static Timer timer; public static void init() { long MIN = 1000*60; long HOUR =60*MIN; long DAY = 24*HOUR; timer.scheduleAtFixedRate(new Cron(), MIN, MIN); new DoubleLaunchChecker().schedule(); // clean up fingerprint once a day timer.scheduleAtFixedRate(new FingerprintCleanupThread(),DAY,DAY); timer.scheduleAtFixedRate(new WorkspaceCleanupThread(),DAY+4*HOUR,DAY); timer.scheduleAtFixedRate(new ComputerRetentionWork(), MIN, MIN); // start monitoring nodes, although there's no hurry. timer.schedule(new SafeTimerTask() { public void doRun() { ComputerSet.initialize(); } }, 1000*10); } }