提交 dbda5305 编写于 作者: K kohsuke

implemented #151.


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@1122 71c3de6d-444a-0410-be80-ed276b4c234a
上级 ca7d7a8d
...@@ -16,6 +16,7 @@ import hudson.tasks.BuildStep; ...@@ -16,6 +16,7 @@ import hudson.tasks.BuildStep;
import hudson.tasks.Builder; import hudson.tasks.Builder;
import hudson.tasks.Publisher; import hudson.tasks.Publisher;
import hudson.triggers.Trigger; import hudson.triggers.Trigger;
import hudson.triggers.Triggers;
import hudson.util.FormFieldValidator; import hudson.util.FormFieldValidator;
import hudson.util.XStream2; import hudson.util.XStream2;
import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItem;
...@@ -721,6 +722,9 @@ public final class Hudson extends JobCollection implements Node { ...@@ -721,6 +722,9 @@ public final class Hudson extends JobCollection implements Node {
for( Descriptor<SCM> scmd : SCMS.SCMS ) for( Descriptor<SCM> scmd : SCMS.SCMS )
result &= scmd.configure(req); result &= scmd.configure(req);
for( Descriptor<Trigger> d : Triggers.TRIGGERS )
result &= d.configure(req);
save(); save();
if(result) if(result)
rsp.sendRedirect("."); // go to the top page rsp.sendRedirect("."); // go to the top page
......
...@@ -8,18 +8,23 @@ import hudson.model.Descriptor; ...@@ -8,18 +8,23 @@ import hudson.model.Descriptor;
import hudson.model.Project; import hudson.model.Project;
import hudson.model.TaskListener; import hudson.model.TaskListener;
import hudson.util.StreamTaskListener; import hudson.util.StreamTaskListener;
import org.kohsuke.stapler.StaplerRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.Date; 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.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.kohsuke.stapler.StaplerRequest;
/** /**
* {@link Trigger} that checks for SCM updates periodically. * {@link Trigger} that checks for SCM updates periodically.
* *
...@@ -27,21 +32,28 @@ import org.kohsuke.stapler.StaplerRequest; ...@@ -27,21 +32,28 @@ import org.kohsuke.stapler.StaplerRequest;
*/ */
public class SCMTrigger extends Trigger { public class SCMTrigger extends Trigger {
/** /**
* Non-null if the polling is in progress. * If we'd like to run another polling run, this is set to true.
*
* <p>
* To avoid submitting more than one polling jobs (which could flood the queue),
* we first use the boolean flag.
*
* @guardedBy this * @guardedBy this
*/ */
private transient boolean pollingScheduled; private transient boolean pollingScheduled;
/** /**
* Non-null if the polling is in progress. * Signal to the polling thread to abort now.
* @guardedBy this
*/ */
private transient Thread pollingThread; private transient boolean abortNow;
/** /**
* Signal to the polling thread to abort now. * Pending polling activity in progress.
* There's at most one polling activity per project at any given point.
*
* @guardedBy this
*/ */
private transient boolean abortNow; private transient Future<?> polling;
public SCMTrigger(String cronTabSpec) throws ANTLRException { public SCMTrigger(String cronTabSpec) throws ANTLRException {
super(cronTabSpec); super(cronTabSpec);
...@@ -64,11 +76,18 @@ public class SCMTrigger extends Trigger { ...@@ -64,11 +76,18 @@ public class SCMTrigger extends Trigger {
* Makes sure that the polling is aborted. * Makes sure that the polling is aborted.
*/ */
public synchronized void abort() throws InterruptedException { public synchronized void abort() throws InterruptedException {
if(pollingThread!=null && pollingThread.isAlive()) { if(polling!=null && !polling.isDone()) {
System.out.println("killing polling"); System.out.println("killing polling");
abortNow = true; abortNow = true;
pollingThread.interrupt(); polling.cancel(true);
pollingThread.join(); try {
polling.get();
} catch (ExecutionException e) {
LOGGER.log(Level.WARNING, "Failed to poll",e);
} catch (CancellationException e) {
// this is fine
}
abortNow = false; abortNow = false;
} }
} }
...@@ -82,61 +101,14 @@ public class SCMTrigger extends Trigger { ...@@ -82,61 +101,14 @@ public class SCMTrigger extends Trigger {
if(b!=null && b.isBuilding()) if(b!=null && b.isBuilding())
return; // build in progress return; // build in progress
if(pollingThread!=null && pollingThread.isAlive()) if(polling!=null && !polling.isDone())
return; // polling already in progress return; // polling already in progress
if(!pollingScheduled) if(!pollingScheduled)
return; // not scheduled return; // not scheduled
pollingScheduled = false; pollingScheduled = false;
pollingThread = new Thread() { polling = DESCRIPTOR.getExecutor().submit(new Runner());
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() {
boolean repeat;
do {
if(runPolling()) {
LOGGER.info("SCM changes detected in "+project.getName());
project.scheduleBuild();
}
if(abortNow)
return; // terminate now
synchronized(SCMTrigger.this) {
repeat = pollingScheduled;
pollingScheduled = false;
}
} while(repeat);
}
};
pollingThread.start();
} }
/** /**
...@@ -150,7 +122,24 @@ public class SCMTrigger extends Trigger { ...@@ -150,7 +122,24 @@ public class SCMTrigger extends Trigger {
return DESCRIPTOR; return DESCRIPTOR;
} }
public static final Descriptor<Trigger> DESCRIPTOR = new Descriptor<Trigger>(SCMTrigger.class) { public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public static final class DescriptorImpl extends Descriptor<Trigger> {
/**
* Used to control the execution of the polling tasks.
*/
transient volatile ExecutorService executor;
DescriptorImpl() {
super(SCMTrigger.class);
// create an executor
update(getPollingThreadCount());
}
public ExecutorService getExecutor() {
return executor;
}
public String getDisplayName() { public String getDisplayName() {
return "Poll SCM"; return "Poll SCM";
} }
...@@ -166,7 +155,51 @@ public class SCMTrigger extends Trigger { ...@@ -166,7 +155,51 @@ public class SCMTrigger extends Trigger {
throw new FormException(e.toString(),e,"scmpoll_spec"); throw new FormException(e.toString(),e,"scmpoll_spec");
} }
} }
};
/**
* Gets the number of concurrent threads used for polling.
*
* @return
* 0 if unlimited.
*/
public int getPollingThreadCount() {
String value = (String)getProperties().get("poll_scm_threads");
if(value==null)
return 0;
return Integer.parseInt(value);
}
public void setPollingThreadCount(int n) {
getProperties().put("poll_scm_threads",String.valueOf(n));
save();
update(n);
}
/**
* Update the {@link ExecutorService} instance.
*/
/*package*/ synchronized void update(int n) {
// fool proof
if(n<0) n=0;
if(n>100) n=0;
// swap to a new one, and shut down the old one gradually
ExecutorService newExec = n==0 ? Executors.newCachedThreadPool() : Executors.newFixedThreadPool(n);
ExecutorService old = executor;
executor = newExec;
if(old!=null)
old.shutdown();
}
public boolean configure(HttpServletRequest 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. * Action object for {@link Project}. Used to display the polling log.
...@@ -194,4 +227,56 @@ public class SCMTrigger extends Trigger { ...@@ -194,4 +227,56 @@ public class SCMTrigger extends Trigger {
} }
private static final Logger LOGGER = Logger.getLogger(SCMTrigger.class.getName()); 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;
}
}
}
} }
...@@ -118,7 +118,14 @@ ...@@ -118,7 +118,14 @@
</s:entry> </s:entry>
</s:section> </s:section>
<!-- build config pane --> <!-- trigger config pane -->
<j:getStatic var="triggers" className="hudson.triggers.Triggers" field="TRIGGERS" />
<j:forEach var="idx" begin="0" end="${size(triggers)-1}">
<j:set var="descriptor" value="${triggers[idx]}" />
<st:include page="${descriptor.globalConfigPage}" from="${descriptor}"/>
</j:forEach>
<!-- builder config pane -->
<j:getStatic var="builds" className="hudson.tasks.BuildStep" field="BUILDERS" /> <j:getStatic var="builds" className="hudson.tasks.BuildStep" field="BUILDERS" />
<j:forEach var="idx" begin="0" end="${size(builds)-1}"> <j:forEach var="idx" begin="0" end="${size(builds)-1}">
<j:set var="descriptor" value="${builds[idx]}" /> <j:set var="descriptor" value="${builds[idx]}" />
...@@ -132,7 +139,7 @@ ...@@ -132,7 +139,7 @@
<st:include page="${descriptor.globalConfigPage}" from="${descriptor}"/> <st:include page="${descriptor.globalConfigPage}" from="${descriptor}"/>
</j:forEach> </j:forEach>
<!-- build config pane --> <!-- publisher config pane -->
<j:getStatic var="pubs" className="hudson.tasks.BuildStep" field="PUBLISHERS" /> <j:getStatic var="pubs" className="hudson.tasks.BuildStep" field="PUBLISHERS" />
<j:forEach var="idx" begin="0" end="${size(pubs)-1}"> <j:forEach var="idx" begin="0" end="${size(pubs)-1}">
<j:set var="descriptor" value="${pubs[idx]}" /> <j:set var="descriptor" value="${pubs[idx]}" />
......
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:if test="${app.jobs.size()>10}">
<!--
unless you have a fair number of projects, this option is likely pointless.
so let's hide this option for new users to avoid confusing them.
-->
<f:section title="SCM Polling">
<f:entry title="Max # of concurrent polling"
help="/help/triggers/SCMTrigger/concurrency.html">
<input class="setting-input" name="poll_scm_threads"
type="text" value="${h.ifThenElse(descriptor.pollingThreadCount==0,'',descriptor.pollingThreadCount)}"/>
</f:entry>
</f:section>
</j:if>
</j:jelly>
\ No newline at end of file
<!-- nothing to configure -->
<j:jelly xmlns:j="jelly:core" />
\ No newline at end of file
target target
.classpath .classpath
*.iml *.iml
.project .project
\ No newline at end of file .settings
work
<div>
If you use SCM polling to trigger a new build for a large number of projects,
you may want to limit the number of concurrent polling activities to avoid
overloading the server.
<p>
Setting a positive number sets the upper bound to the number of concurrent polling.
Leaving the field empty will make it unbounded.
</div>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册