Index: core/src/main/java/hudson/model/AbstractProject.java =================================================================== --- core/src/main/java/hudson/model/AbstractProject.java (revision 15370) +++ core/src/main/java/hudson/model/AbstractProject.java (working copy) @@ -27,6 +27,7 @@ import hudson.FeedAdapter; import hudson.FilePath; import hudson.Launcher; +import hudson.Util; import hudson.maven.MavenModule; import hudson.model.Cause.UserCause; import hudson.model.Cause.LegacyCodeCause; @@ -745,21 +746,48 @@ return Collections.emptySet(); } - public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException { - SCM scm = getScm(); + public boolean checkout(final AbstractBuild build, final Launcher launcher, final BuildListener listener, final File changelogFile) throws IOException { + final SCM scm = getScm(); if(scm==null) return true; // no SCM + final boolean[] result = new boolean[1]; + final Exception[] exception = new Exception[1]; + Runnable task = new Runnable() { + public void run() { try { FilePath workspace = getWorkspace(); workspace.mkdirs(); - return scm.checkout(build, launcher, workspace, listener, changelogFile); + result[0] = scm.checkout(build, launcher, workspace, listener, changelogFile); + } catch (IOException e) { + exception[0] = e; } catch (InterruptedException e) { + exception[0] = e; + } + } + }; + + // Acquire resource for SCMTrigger so poll won't run while we checkout/update + SCMTrigger scmt = getTrigger(SCMTrigger.class); + if (scmt==null) + task.run(); + else try { + Hudson.getInstance().getQueue().execute(task, scmt); + } + catch (InterruptedException e) { + exception[0] = e; + } + + if (exception[0] instanceof IOException) { + throw (IOException)exception[0]; + } + else if (exception[0] instanceof InterruptedException) { listener.getLogger().println(Messages.AbstractProject_ScmAborted()); - LOGGER.log(Level.INFO,build.toString()+" aborted",e); - return false; + LOGGER.log(Level.INFO,build.toString()+" aborted",exception[0]); + result[0] = false; } + return result[0]; } /** Index: core/src/main/java/hudson/triggers/SCMTrigger.java =================================================================== --- core/src/main/java/hudson/triggers/SCMTrigger.java (revision 15370) +++ core/src/main/java/hudson/triggers/SCMTrigger.java (working copy) @@ -31,6 +31,9 @@ import hudson.model.Hudson; import hudson.model.Item; import hudson.model.Project; +import hudson.model.Resource; +import hudson.model.ResourceActivity; +import hudson.model.ResourceList; import hudson.model.SCMedItem; import hudson.model.AdministrativeMonitor; import hudson.util.StreamTaskListener; @@ -61,7 +64,7 @@ * * @author Kohsuke Kawaguchi */ -public class SCMTrigger extends Trigger { +public class SCMTrigger extends Trigger implements ResourceActivity { /** * If we'd like to run another polling run, this is set to true. * @@ -411,6 +414,16 @@ } public void run() { + try { + // Acquire resource so we don't poll while checkout/update is running + Hudson.getInstance().getQueue().execute(runTask, SCMTrigger.this); + } catch (InterruptedException ex) { + LOGGER.info("Aborted"); + } + } + + private Runnable runTask = new Runnable() { + public void run() { String threadName = Thread.currentThread().getName(); Thread.currentThread().setName("SCM polling for "+job); try { @@ -421,11 +434,11 @@ if(pollingScheduled) { pollingScheduled = false; startTime = System.currentTimeMillis(); - getDescriptor().items.add(this); + getDescriptor().items.add(Runner.this); try { foundChanges = runPolling(); } finally { - getDescriptor().items.remove(this); + getDescriptor().items.remove(Runner.this); } } } finally { @@ -447,8 +460,24 @@ Thread.currentThread().setName(threadName); } } + }; } + private transient Resource myResource; + + @Override public void start(SCMedItem p, boolean newInstance) { + super.start(p, newInstance); + myResource = new Resource(null, "SCMTrigger for " + p.asProject().getDisplayName()); + } + + public ResourceList getResourceList() { + return new ResourceList().w(myResource); + } + + public String getDisplayName() { + return Messages.SCMTrigger_DisplayName(); + } + public static class SCMTriggerCause extends Cause { @Override public String getShortDescription() { Index: test/src/test/java/hudson/triggers/SCMTriggerTest.java =================================================================== --- test/src/test/java/hudson/triggers/SCMTriggerTest.java (revision 0) +++ test/src/test/java/hudson/triggers/SCMTriggerTest.java (revision 0) @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.triggers; + +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.Cause; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Hudson; +import hudson.model.TaskListener; +import hudson.scm.NullSCM; +import org.jvnet.hudson.test.Bug; +import org.jvnet.hudson.test.HudsonTestCase; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Future; + +/** + * @author Alan Harder + */ +public class SCMTriggerTest extends HudsonTestCase { + /** + * Make sure that SCMTrigger doesn't trigger another build when a build has just started, + * but not yet completed its SCM update. + */ + @Bug(2671) + public void testSimultaneousPollAndBuild() throws Exception { + FreeStyleProject p = createFreeStyleProject(); + final Object notifier = new Object(); + p.setScm(new TestSCM(notifier)); + SCMTrigger.DESCRIPTOR.synchronousPolling = true; + SCMTrigger trigger = new SCMTrigger("0 0 1 1 0"); + p.addTrigger(trigger); + trigger.start(p, true); + + Future build = p.scheduleBuild2(0, new Cause.UserCause()); + // pollSCM as soon as build starts its checkout/update + synchronized (notifier) { notifier.wait(); } + trigger.run(); + boolean result = Hudson.getInstance().getQueue().cancel(p); + build.get(); // let mock build finish + assertFalse("SCM-poll after build has started should wait until that build finishes SCM-update", result); + } + + private static class TestSCM extends NullSCM { + private int myRev = 1; + private Object notifier; + + private TestSCM(Object notifier) { this.notifier = notifier; } + + @Override synchronized + public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath dir, TaskListener listener) throws IOException { + return myRev < 2; + } + + @Override + public boolean checkout(AbstractBuild build, Launcher launcher, FilePath remoteDir, BuildListener listener, File changeLogFile) throws IOException, InterruptedException { + synchronized (notifier) { notifier.notify(); } + Thread.sleep(400); // processing time for mock update + synchronized (this) { if (myRev < 2) myRev = 2; } + return super.checkout(build, launcher, remoteDir, listener, changeLogFile); + } + } +} Property changes on: test/src/test/java/hudson/triggers/SCMTriggerTest.java ___________________________________________________________________ Name: svn:eol-style + native