All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.triggers.SCMTrigger Maven / Gradle / Ivy

package hudson.triggers;

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;
import hudson.util.StreamTaskListener;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.File;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * {@link Trigger} that checks for SCM updates periodically.
 *
 * @author Kohsuke Kawaguchi
 */
public class SCMTrigger extends Trigger {
    /**
     * If we'd like to run another polling run, this is set to true.
     *
     * 

* To avoid submitting more than one polling jobs (which could flood the queue), * we first use the boolean flag. * * @guardedBy this */ private transient volatile boolean pollingScheduled; /** * This lock is used to control the mutual exclusion of the SCM activity, * so that the build and polling don't happen at the same time. */ private transient ReentrantLock lock; @DataBoundConstructor public SCMTrigger(String scmpoll_spec) throws ANTLRException { super(scmpoll_spec); lock = new ReentrantLock(); } public ReentrantLock getLock() { return lock; } protected Object readResolve() throws ObjectStreamException { lock = new ReentrantLock(); return super.readResolve(); } public void run() { if(pollingScheduled || Hudson.getInstance().isQuietingDown()) return; // noop pollingScheduled = true; LOGGER.fine("Scheduling a polling for "+job); if (DESCRIPTOR.synchronousPolling) { LOGGER.fine("Running 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. LOGGER.fine("scheduling the trigger to (asynchronously) run"); DESCRIPTOR.getExecutor().submit(new Runner()); } } public Action getProjectAction() { return new SCMAction(); } /** * Returns the file that records the last/current polling activity. */ public File getLogFile() { return new File(job.getRootDir(),"scm-polling.log"); } public DescriptorImpl getDescriptor() { return DESCRIPTOR; } public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); public static final class DescriptorImpl extends TriggerDescriptor { /** * Used to control the execution of the polling tasks. */ 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. */ final transient Set items = Collections.synchronizedSet(new HashSet()); /** * Max number of threads for SCM polling. * 0 for unbounded. */ private int maximumThreads; DescriptorImpl() { super(SCMTrigger.class); load(); /* * Need to resize the thread pool here in case there is no existing configuration file for SCMTrigger as * setPollingThreadCount() is not called in this case */ resizeThreadPool(); } public boolean isApplicable(Item item) { return item instanceof SCMedItem; } public ExecutorService getExecutor() { return executor; } /** * Gets the snapshot of {@link SCMedItem}s that are being polled at this very moment. * Designed for trouble-shooting probe. */ public List getItemsBeingPolled() { return Arrays.asList(items.toArray(new SCMedItem[0])); } public String getDisplayName() { return Messages.SCMTrigger_DisplayName(); } public String getHelpFile() { return "/help/project-config/poll-scm.html"; } /** * Gets the number of concurrent threads used for polling. * * @return * 0 if unlimited. */ public int getPollingThreadCount() { return maximumThreads; } /** * Sets the number of concurrent threads used for SCM polling and resizes the thread pool accordingly * @param n number of concurrent threads, zero or less means unlimited, maximum is 100 */ public void setPollingThreadCount(int n) { // fool proof if(n<0) n=0; if(n>100) n=100; maximumThreads = n; resizeThreadPool(); } /** * Update the {@link ExecutorService} instance. */ /*package*/ synchronized void resizeThreadPool() { // swap to a new one, and shut down the old one gradually ExecutorService newExec = maximumThreads==0 ? Executors.newCachedThreadPool() : Executors.newFixedThreadPool(maximumThreads); ExecutorService old = executor; executor = newExec; if(old!=null) old.shutdown(); } public boolean configure(StaplerRequest req) throws FormException { String t = req.getParameter("poll_scm_threads"); if(t==null || t.length()==0) setPollingThreadCount(0); else setPollingThreadCount(Integer.parseInt(t)); // Save configuration save(); return super.configure(req); } } /** * Action object for {@link Project}. Used to display the polling log. */ public final class SCMAction implements Action { public AbstractProject getOwner() { return job.asProject(); } public String getIconFileName() { return "clipboard.gif"; } public String getDisplayName() { return Messages.SCMTrigger_getDisplayName(job.getScm().getDescriptor().getDisplayName()); } public String getUrlName() { return "scmPollLog"; } public String getLog() throws IOException { return Util.loadFile(getLogFile()); } } private static final Logger LOGGER = Logger.getLogger(SCMTrigger.class.getName()); /** * {@link Runnable} that actually performs polling. */ private class Runner implements Runnable { private boolean runPolling() { try { // to make sure that the log file contains up-to-date text, // don't do buffering. StreamTaskListener listener = new StreamTaskListener(getLogFile()); try { PrintStream logger = listener.getLogger(); long start = System.currentTimeMillis(); logger.println("Started on "+new Date().toLocaleString()); boolean result = job.pollSCMChanges(listener); logger.println("Done. Took "+ Util.getTimeSpanString(System.currentTimeMillis()-start)); if(result) logger.println("Changes found"); else logger.println("No changes"); return result; } finally { listener.close(); } } catch (IOException e) { LOGGER.log(Level.SEVERE,"Failed to record SCM polling",e); return false; } catch (RuntimeException e) { LOGGER.log(Level.SEVERE,"Failed to record SCM polling",e); throw e; } catch (Error e) { LOGGER.log(Level.SEVERE,"Failed to record SCM polling",e); throw e; } } public void run() { try { while(pollingScheduled) { getLock().lockInterruptibly(); boolean foundChanges=false; try { if(pollingScheduled) { pollingScheduled = false; getDescriptor().items.add(job); try { foundChanges = runPolling(); } finally { getDescriptor().items.remove(job); } } } finally { getLock().unlock(); } if(foundChanges) { String name = " #"+job.asProject().getNextBuildNumber(); if(job.scheduleBuild()) { LOGGER.info("SCM changes detected in "+ job.getName()+". Triggering "+name); } else { LOGGER.info("SCM changes detected in "+ job.getName()+". Job is already in the queue"); } } } } catch (InterruptedException e) { LOGGER.info("Aborted"); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy