hudson.triggers.SCMTrigger Maven / Gradle / Ivy
package hudson.triggers;
import antlr.ANTLRException;
import hudson.Util;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.Descriptor;
import hudson.model.Project;
import hudson.model.TaskListener;
import hudson.util.StreamTaskListener;
import org.kohsuke.stapler.StaplerRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Date;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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 boolean pollingScheduled;
/**
* Signal to the polling thread to abort now.
*/
private transient boolean abortNow;
/**
* Pending polling activity in progress.
* There's at most one polling activity per project at any given point.
*
* @guardedBy this
*/
private transient Future> polling;
public SCMTrigger(String cronTabSpec) throws ANTLRException {
super(cronTabSpec);
}
protected synchronized void run() {
if(pollingScheduled)
return; // noop
pollingScheduled = true;
// otherwise do it now
startPolling();
}
public Action getProjectAction() {
return new SCMAction();
}
/**
* Makes sure that the polling is aborted.
*/
public synchronized void abort() throws InterruptedException {
if(polling!=null && !polling.isDone()) {
System.out.println("killing polling");
abortNow = true;
polling.cancel(true);
try {
polling.get();
} catch (ExecutionException e) {
LOGGER.log(Level.WARNING, "Failed to poll",e);
} catch (CancellationException e) {
// this is fine
}
abortNow = false;
}
}
/**
* Start polling if it's scheduled.
*/
public synchronized void startPolling() {
Build b = project.getLastBuild();
if(b!=null && b.isBuilding())
return; // build in progress
if(polling!=null && !polling.isDone())
return; // polling already in progress
if(!pollingScheduled)
return; // not scheduled
pollingScheduled = false;
polling = DESCRIPTOR.getExecutor().submit(new Runner());
}
/**
* Returns the file that records the last/current polling activity.
*/
public File getLogFile() {
return new File(project.getRootDir(),"scm-polling.log");
}
public Descriptor getDescriptor() {
return DESCRIPTOR;
}
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public static final class DescriptorImpl extends Descriptor {
/**
* Used to control the execution of the polling tasks.
*/
transient volatile ExecutorService executor;
/**
* Max number of threads for SCM polling.
* 0 for unbounded.
*/
private int maximumThreads;
DescriptorImpl() {
super(SCMTrigger.class);
load();
// create an executor
update();
}
public ExecutorService getExecutor() {
return executor;
}
public String getDisplayName() {
return "Poll SCM";
}
public String getHelpFile() {
return "/help/project-config/poll-scm.html";
}
public Trigger newInstance(StaplerRequest req) throws FormException {
try {
return new SCMTrigger(req.getParameter("scmpoll_spec"));
} catch (ANTLRException e) {
throw new FormException(e.toString(),e,"scmpoll_spec");
}
}
/**
* Gets the number of concurrent threads used for polling.
*
* @return
* 0 if unlimited.
*/
public int getPollingThreadCount() {
return maximumThreads;
}
public void setPollingThreadCount(int n) {
// fool proof
if(n<0) n=0;
if(n>100) n=100;
maximumThreads = n;
save();
}
/**
* Update the {@link ExecutorService} instance.
*/
/*package*/ synchronized void update() {
// 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));
return super.configure(req);
}
}
/**
* Action object for {@link Project}. Used to display the polling log.
*/
public final class SCMAction implements Action {
public Project getOwner() {
return project;
}
public String getIconFileName() {
return "clipboard.gif";
}
public String getDisplayName() {
return project.getScm().getDescriptor().getDisplayName()+" Polling Log";
}
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.
OutputStream fos = new FileOutputStream(getLogFile());
TaskListener listener = new StreamTaskListener(fos);
try {
LOGGER.info("Polling SCM changes of "+project.getName());
PrintStream logger = listener.getLogger();
long start = System.currentTimeMillis();
logger.println("Started on "+new Date().toLocaleString());
boolean result = project.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 {
fos.close();
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE,"Failed to record SCM polling",e);
return false;
}
}
public void run() {
if(runPolling()) {
LOGGER.info("SCM changes detected in "+project.getName());
project.scheduleBuild();
}
synchronized(SCMTrigger.this) {
if(abortNow)
return; // terminate now without queueing the next one.
if(pollingScheduled) {
// schedule a next run
polling = DESCRIPTOR.getExecutor().submit(new Runner());
}
pollingScheduled = false;
}
}
}
}