diff --git a/src/main/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStep.java b/src/main/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStep.java index c445d0b..42eb5d8 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStep.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStep.java @@ -25,7 +25,9 @@ package org.jenkinsci.plugins.workflow.steps.scm; import com.google.common.collect.ImmutableSet; +import hudson.AbortException; import hudson.FilePath; +import hudson.Functions; import hudson.Launcher; import hudson.model.Run; import hudson.model.TaskListener; @@ -33,6 +35,7 @@ import hudson.scm.SCM; import hudson.scm.SCMRevisionState; import java.io.File; +import java.io.InterruptedIOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.attribute.PosixFilePermissions; @@ -42,6 +45,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.model.Jenkins; import org.jenkinsci.plugins.workflow.steps.Step; import org.jenkinsci.plugins.workflow.steps.StepContext; import org.jenkinsci.plugins.workflow.steps.StepDescriptor; @@ -62,11 +66,11 @@ public abstract class SCMStep extends Step { public boolean isPoll() { return poll; } - + @DataBoundSetter public void setPoll(boolean poll) { this.poll = poll; } - + public boolean isChangelog() { return changelog; } @@ -126,7 +130,30 @@ public final void checkout(Run run, FilePath workspace, TaskListener listen } } } - scm.checkout(run, launcher, workspace, listener, changelogFile, baseline); + + for (int retryCount = Jenkins.get().getScmCheckoutRetryCount(); retryCount >= 0; retryCount--) { + try { + scm.checkout(run, launcher, workspace, listener, changelogFile, baseline); + break; + } catch (InterruptedIOException e) { + throw e; + } catch (Exception e) { + // We follow the same exception output behavior as jenkinsci/workflow-cps-plugin#147, + // but throw up the original exception if this is the last attempt + if (e instanceof AbortException && e.getMessage() != null) { + listener.error(e.getMessage()); + } else { + Functions.printStackTrace(e, listener.error("Checkout failed")); + } + if (retryCount == 0) { + listener.error("Maximum checkout retry attempts reached, aborting");// all attempts failed + throw e; + } + } + listener.getLogger().println("Retrying after 10 seconds"); + Thread.sleep(10000); + } + if (changelogFile != null && changelogFile.length() == 0 && changelogOriginalModifiedDate != null && changelogFile.lastModified() == changelogOriginalModifiedDate) { // JENKINS-57918/JENKINS-59560/FakeChangeLogSCM: Some SCMs don't write anything to the changelog file in some diff --git a/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStepTest.java index 064e571..0b91f30 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/SCMStepTest.java @@ -238,4 +238,26 @@ public ChangeLogSet parse(Run build, RepositoryBro public static class DescriptorImpl extends NullSCM.DescriptorImpl { } } + + + @Test + public void scmRetryFromFakeUnstableChangeLogSCM() { + rr.then(r -> { + r.jenkins.setScmCheckoutRetryCount(2); + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "import org.jenkinsci.plugins.workflow.steps.scm.UnstableSCM\n" + + "def testSCM = new UnstableSCM(2)\n" + + "testSCM.addChange().withAuthor(/alice$BUILD_NUMBER/)\n" + + "node() {\n" + + " checkout(testSCM)\n" + + "}", false)); + WorkflowRun b = r.buildAndAssertSuccess(p); + assertThat(b.getCulpritIds(), Matchers.equalTo(Collections.singleton("alice1"))); + r.assertLogContains("Checkout failed", b); + r.assertLogContains("IO Exception happens", b); + r.assertLogContains("Retrying after 10 seconds", b); + assertThat(b.getCulpritIds(), Matchers.equalTo(Collections.singleton("alice1"))); + }); + } } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/UnstableSCM.java b/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/UnstableSCM.java new file mode 100644 index 0000000..e2271a9 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/steps/scm/UnstableSCM.java @@ -0,0 +1,30 @@ +package org.jenkinsci.plugins.workflow.steps.scm; + +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.scm.SCMRevisionState; + +import java.io.File; +import java.io.IOException; + +public class UnstableSCM extends org.jvnet.hudson.test.FakeChangeLogSCM { + private int failedCount; + + public UnstableSCM(int failedCount) { + this.failedCount = failedCount; + } + + @Override + public void checkout(Run build, Launcher launcher, FilePath remoteDir, TaskListener listener, File changeLogFile, SCMRevisionState baseline) throws IOException, InterruptedException { + try { + if (failedCount > 0) { + throw new IOException("IO Exception happens"); + } + super.checkout(build, launcher, remoteDir, listener, changeLogFile, baseline); + } finally { + failedCount--; + } + } +}