From 991f04fe6daee5b1dcfa9150c5a2f9f533be1328 Mon Sep 17 00:00:00 2001 From: gongy Date: Fri, 9 Dec 2022 14:06:07 +0800 Subject: [PATCH 1/2] Honor SCM checkout retry count Inspired by jenkinsci/workflow-cps-plugin#147 --- .../plugins/workflow/steps/scm/SCMStep.java | 30 ++++++++++++++++++- .../workflow/steps/scm/SCMStepTest.java | 21 +++++++++++++ .../workflow/steps/scm/UnstableSCM.java | 30 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/jenkinsci/plugins/workflow/steps/scm/UnstableSCM.java 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..a7473a7 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; @@ -126,7 +130,31 @@ 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 (AbortException e) { + // abort exception might have a null message. + // If so, just skip echoing it. + if (e.getMessage() != null) { + listener.error(e.getMessage()); + } + } catch (InterruptedIOException e) { + throw e; + } catch (Exception e) { + // checkout error not yet reported + Functions.printStackTrace(e, listener.error("Checkout failed")); + } + + if (retryCount == 0) // all attempts failed + throw new AbortException("Maximum checkout retry attempts reached, aborting"); + + 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..d7d76d9 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,25 @@ public ChangeLogSet parse(Run build, RepositoryBro public static class DescriptorImpl extends NullSCM.DescriptorImpl { } } + + + @Test + public void scmRetryFromFakeChangeLogSCM() { + 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); + }); + } } 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--; + } + } +} From 99cce8e01b4c217198b7c0818fda5ca9923957e4 Mon Sep 17 00:00:00 2001 From: gongy Date: Thu, 15 Dec 2022 09:05:14 +0800 Subject: [PATCH 2/2] Throw up the original exception if this is the last attempt --- .../plugins/workflow/steps/scm/SCMStep.java | 27 +++++++++---------- .../workflow/steps/scm/SCMStepTest.java | 3 ++- 2 files changed, 15 insertions(+), 15 deletions(-) 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 a7473a7..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 @@ -66,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; } @@ -135,22 +135,21 @@ public final void checkout(Run run, FilePath workspace, TaskListener listen try { scm.checkout(run, launcher, workspace, listener, changelogFile, baseline); break; - } catch (AbortException e) { - // abort exception might have a null message. - // If so, just skip echoing it. - if (e.getMessage() != null) { - listener.error(e.getMessage()); - } } catch (InterruptedIOException e) { throw e; } catch (Exception e) { - // checkout error not yet reported - Functions.printStackTrace(e, listener.error("Checkout failed")); + // 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; + } } - - if (retryCount == 0) // all attempts failed - throw new AbortException("Maximum checkout retry attempts reached, aborting"); - listener.getLogger().println("Retrying after 10 seconds"); Thread.sleep(10000); } 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 d7d76d9..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 @@ -241,7 +241,7 @@ public static class DescriptorImpl extends NullSCM.DescriptorImpl { } @Test - public void scmRetryFromFakeChangeLogSCM() { + public void scmRetryFromFakeUnstableChangeLogSCM() { rr.then(r -> { r.jenkins.setScmCheckoutRetryCount(2); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); @@ -257,6 +257,7 @@ public void scmRetryFromFakeChangeLogSCM() { 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"))); }); } }