From 7b934f1d0318584c05fb45b4bbe80e70c1a98166 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 6 Aug 2024 17:52:10 -0400 Subject: [PATCH 1/2] Avoid infinite loops due to corrupted flow graphs in StandardGraphLookupView and LinearBlockHoppingScanner and improve resumption error handling --- .../workflow/flow/FlowExecutionList.java | 25 +++++++--- .../graph/StandardGraphLookupView.java | 7 ++- .../LinearBlockHoppingScanner.java | 7 ++- .../workflow/flow/FlowExecutionListTest.java | 22 +++++++++ .../jobs/test0/builds/1/build.xml | 46 ++++++++++++++++++ .../jobs/test0/builds/1/log | 13 +++++ .../jobs/test0/builds/1/log-index | 5 ++ .../jobs/test0/builds/1/program.dat | Bin 0 -> 5265 bytes .../jobs/test0/builds/1/workflow/10.xml | 26 ++++++++++ .../jobs/test0/builds/1/workflow/2.xml | 12 +++++ .../jobs/test0/builds/1/workflow/3.xml | 26 ++++++++++ .../jobs/test0/builds/1/workflow/4.xml | 19 ++++++++ .../jobs/test0/builds/1/workflow/5.xml | 26 ++++++++++ .../jobs/test0/builds/1/workflow/6.xml | 16 ++++++ .../jobs/test0/builds/1/workflow/7.xml | 15 ++++++ .../jobs/test0/builds/1/workflow/8.xml | 26 ++++++++++ .../jobs/test0/builds/1/workflow/9.xml | 16 ++++++ .../jobs/test0/builds/legacyIds | 0 .../jobs/test0/config.xml | 11 +++++ .../jobs/test0/nextBuildNumber | 1 + ...lugins.workflow.flow.FlowExecutionList.xml | 7 +++ 21 files changed, 317 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/program.dat create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/legacyIds create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java index e0a0809f..fe8effbf 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java @@ -362,20 +362,31 @@ private static final class ParallelResumer { nodes.put(n, se); } else { LOGGER.warning(() -> "Could not find FlowNode for " + se + " so it will not be resumed"); + // TODO: Should we call se.getContext().onFailure()? } } catch (IOException | InterruptedException x) { LOGGER.log(Level.WARNING, "Could not look up FlowNode for " + se + " so it will not be resumed", x); + // TODO: Should we call se.getContext().onFailure()? } } - for (FlowNode n : nodes.keySet()) { - LinearBlockHoppingScanner scanner = new LinearBlockHoppingScanner(); - scanner.setup(n); - for (FlowNode parent : scanner) { - if (parent != n && nodes.containsKey(parent)) { - enclosing.put(n, parent); - break; + try { + for (FlowNode n : nodes.keySet()) { + LinearBlockHoppingScanner scanner = new LinearBlockHoppingScanner(); + scanner.setup(n); + for (FlowNode parent : scanner) { + if (parent != n && nodes.containsKey(parent)) { + enclosing.put(n, parent); + break; + } } } + } catch (Exception e) { + // TODO: Should we instead just try to resume steps with no respect to topological order? + // There is something wrong with the FlowGraph. Kill all of the steps so the build fails instead of hanging forever. + for (StepExecution se : executions) { + se.getContext().onFailure(e); + } + onCompletion.run(); } } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java index e721410a..8f4f0f04 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java @@ -11,7 +11,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -119,8 +121,11 @@ BlockEndNode bruteForceScanForEnd(@NonNull BlockStartNode start) { /** Do a brute-force scan for the enclosing blocks **/ BlockStartNode bruteForceScanForEnclosingBlock(@NonNull final FlowNode node) { FlowNode current = node; - + Set visited = new HashSet<>(); while (!(current instanceof FlowStartNode)) { // Hunt back for enclosing blocks, a potentially expensive operation + if (!visited.add(current.getId())) { + throw new IllegalStateException("Loop in flow graph for " + node.getExecution() + " involving " + current); + } if (current instanceof BlockEndNode) { // Hop over the block to the start BlockStartNode start = ((BlockEndNode) current).getStartNode(); diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java index 970a0b54..3e4209dd 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java @@ -32,8 +32,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.NotThreadSafe; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -85,9 +87,12 @@ protected void setHeads(@NonNull Collection heads) { @CheckForNull protected FlowNode jumpBlockScan(@CheckForNull FlowNode node, @NonNull Collection blacklistNodes) { FlowNode candidate = node; - + Set visited = new HashSet<>(); // Find the first candidate node preceding a block... and filtering by blacklist while (candidate instanceof BlockEndNode) { + if (!visited.add(candidate.getId())) { + throw new IllegalStateException("Loop in flow graph for " + candidate.getExecution() + " involving " + candidate); + } candidate = ((BlockEndNode) candidate).getStartNode(); if (blacklistNodes.contains(candidate)) { return null; diff --git a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java index 8cf59f30..47a15707 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -61,6 +61,7 @@ import org.jvnet.hudson.test.LoggerRule; import org.jvnet.hudson.test.JenkinsSessionRule; import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.recipes.LocalData; import org.kohsuke.stapler.DataBoundConstructor; public class FlowExecutionListTest { @@ -160,6 +161,27 @@ public class FlowExecutionListTest { }); } + @LocalData + @Test public void resumeStepExecutionsWithCorruptFlowGraphWithLoop() throws Throwable { + // LocalData created using the following snippet while the build was waiting in the _second_ sleep, except + // for build.xml, which was captured during the sleep step. The StepEndNode for the stage was then adjusted to + // have its startId point to the timeout step's StepStartNode, creating a loop. + /* + sessions.then(r -> { + var stuck = r.createProject(WorkflowJob.class); + stuck.setDefinition(new CpsFlowDefinition("stage('stage') { sleep 30 }; timeout(time: 10) { sleep 30 }", true)); + var b = stuck.scheduleBuild2(0).waitForStart(); + System.out.println(b.getRootDir()); + r.waitForCompletion(b); + }); + */ + sessions.then(r -> { + var p = r.jenkins.getItemByFullName("test0", WorkflowJob.class); + var b = p.getBuildByNumber(1); + r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); + }); + } + public static class NonResumableStep extends Step implements Serializable { public static final long serialVersionUID = 1L; @DataBoundConstructor diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml new file mode 100644 index 00000000..d2b7e367 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml @@ -0,0 +1,46 @@ + + + 1 + 1722979974600 + 1722979974615 + 0 + UTF-8 + false + + SUCCESS + + + MAX_SURVIVABILITY + + + flowNode + 101709541 + + + classLoad + 124446251 + + + run + 200459289 + + + parse + 166818958 + + + saveProgram + 57936125 + + + + true + 5 + 1:5 + 2 + false + false + + false + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log new file mode 100644 index 00000000..f8c5883d --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log @@ -0,0 +1,13 @@ +Started +ha:////4LSn6sjy3SQJvLr4M0Jcw5zYiu9jzzxAhCQhf5ciKGcGAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline +ha:////4FLPcBhXXN+T+Uy5Bq+9NjiuG45smS/CK+MQ8sUKcTBsAAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycohUghExsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jduZBmjwAAAAA==[Pipeline] stage +ha:////4MPOLP4Go7JuW3NkAKhksIfyE+NroTrcISNM8xfRFLQ8AAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycoh0gA0xsUZZOEFIQkgb/d8mKe3EibgadyBQiQlLlmxL1nu+oE4RjhQdby12HpP2vA+jK4lPFLtroIm3dOGaMFGwXNpJkrGnpUrKFhaxClYC1hZ1oOTRZdiIVt1VExS65pxj2Q4CKm8GeAAThZxVzN8yR9jeRpMIf5y/AJj7DGxXvP/86jfoP95RwAAAAA==[Pipeline] { (stage) +ha:////4E53KhegWm+s/q0TJkIC5MI9kTq62Eqnzz2Qdi1URRTJAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQTbaGWsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gdpL1fxvAAAAA==[Pipeline] sleep +Sleeping for 30 sec +ha:////4G8hLCAAqKEvMe92YhTNPJa4MSOZpWK2lhgTDgEHbCXUAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQTbGBtjZUtoPAGyiLDkfxZYdytP5NW8g8RNrJxkknnTvNcb1jnBiZLl3mDvMGvHYxhtXXyi1N8CTdzTlWvCTMFwaSZJnTkvKKkYWMIaWAnYGNSBskNbYCu8eqg2KLTtpaT6HQU0rhvgCUxUc1GpfGFOsLuPXSb8ef4KYI6xADvU7j8OXFZ7vAAAAA==[Pipeline] } +ha:////4AnScT3OQumBbV+luAyxvhEcCl/8MozDCcq/aC6iNLpjAAAAox+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQRbWFgYK1tC4wmQRYQl/7PAult5Iq/mHSRuYuUkk8yb5r3esM4JTpQs9wZ7h1k7HsNo6+ITpf4WaOKerlwTZgqGSzNJ6sx5QUnFwBLWwErAxqAOlB3aAlvh1UO1QaFtLyXV7yigcd0AT2CimotK5Qtzgt197DLhz/NXAHOMBdihdv8BHeBS2LwAAAA=[Pipeline] // stage +ha:////4PTj6M4JscP6Gdk49EfTAaLMCwLZYd9IOq+brFvOiJPAAAAAph+LCAAAAAAAAP9tjUEKwjAURH8qXbh16SFScCWIK7chG08QkxjThv/bJLVdeSKv5h2MFlw5MDAzMLznC+oU4UjR8dZi5zFpz/swupL4RLG7Bpp4SxeuCRMFy6WdJBl7WqqkbGERq2AlYG1RB0oeXYaNaNVdNUGha845lu0goPJmgAcwUchZxfwtc4TtbTSJ8Mf5C4C5z8B2xfvPr34DrZTeycAAAAA=[Pipeline] timeout +Timeout set to expire in 10 min +ha:////4M9FYx/jFzgoF1Ji4m6uzCtxEvJBQzBzYoKBBTbKepUTAAAApR+LCAAAAAAAAP9tjTEOwjAUQ3+KOrAycoh0BSEm1igLJwhJCGmj/9skpZ04EVfjDgQqMWHJkm3Jes8X1CnCkaLjrcXOY9Ke92F0JfGJYncNNPGWLlwTJgqWSztJMva0VEnZwiJWwUrA2qIOlDy6DBvRqrtqgkLXnHMs20FA5c0AD2CikLOK+VvmCNvbaBLhj/MXAHOfge2K959f/QbB16AVwAAAAA==[Pipeline] { +ha:////4O/MG/IybiYM4oG30m877qNjUwTyRLwWY87qTVAOsZwoAAAAoh+LCAAAAAAAAP9tjTEOwjAQBDdBFLSUPMKBEiEqWisNLzCJMU6su2BfSCpexNf4AxGRqNhqZ5p5vbFMEUeOTjWWWk+p8qoLvZueGji218CDaviiKqbEwarSDiXX9jRjyWIxL8ux0FhZqgInT06w1o15mCIYcsVZ4uQOGrmv73gi01NZTJQvjBGbW18npl/nbwBjJ8j2gny37T6VOYoyvQAAAA==[Pipeline] sleep +Sleeping for 30 sec diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index new file mode 100644 index 00000000..bfafbf78 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index @@ -0,0 +1,5 @@ +1232 5 +1252 +2157 8 +2189 +2789 10 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/program.dat new file mode 100644 index 0000000000000000000000000000000000000000..aeae086b3dd4f5e7c9aba3ba7f60a06962c2eb97 GIT binary patch literal 5265 zcmbVQYi}G^8J^jl_4<;eN!u){f+|C|2_BSY62?<=5x3pXMIsF~bpS!jS>28=;xyuML9_1ROWa8lPXx#8H0Ex+21Kl|9#83YM{<2W1)R{^nO7elr-|4^TFFfTET#xk; zN7PDCDs^W#k4x1^h=pZo1+NzRWQzJc8jgd^pj%m&&lWKIzWR9I4yvX4z)%0^;%wnw ze;Nb3t?Z1m2qSd6+~-w0;kfQ`y@)%Bx4>bj2Ws|`S4(S0(%KAsW> z6N>?lFl9&Bcs#WnD%-;_!e8K(Bm!!68KZlXIWImsa}wCFwGPAVI8pa%MS9~j+i`aP zo_}9}U=aP$&a9@U(i9w|yf$dG{gF#z{=pvR%gBuS1#xvy)^4qPA3Ac;oX z0K{n+iYO_Co-^-*B5^}UVt(mRw0xAjq~Mi>3T3U_xM%MT>$?kd$F5_s)}~uWw?llk z0fBSI$~$(LAn?EjBj&zj-y<#kunK1TKKWt|lrbaRW#II#vPuRg0%>c!VP81&6-R{J zm6uJRhzPzEDtoq3vBJNS^1$H|;{4RB+xx!x$G=Mz4L}?u{cs*oSRdb7qOmkUAcIa}JL1Llb!PV69AZ${n9E!ci4V`Vq3)Jfi`z@nk>f&}Y6+EOw}(uznU zhffN}CP$J+I7l-p2428bVnllDLRy>?E_gu$0KBgy@L@S}6U?l&!{v@|M_y1J@$HzX z(LL+={0P{9>t3~nx+}03*^5>-N(00Euhl~=kveIHdEGEq*guAK^A=uF8G5k2fj^JJ zH-!wn-oVH~=5105`N6z}yykNrYQTGp^s5R{^*IG*4avpL7U@#Ew;{XP+fj_>-iMQYv*-Y zeb7wCH+TaDyXcmRFS`Lb^IotZ=4CD39&zO1V90e*f?*2wA4so5bY0dK2;WJIH7Tm- zyQ&ik_J2Ryb$a0ESD%IUIy(bAptMeXjeJm1d7+Nc{APSygzFx1ly6m+wB$Z`QccD{R%dng5_U5D59rok0hl8D#1u$39r`g%N zfLRGh881UoLoDwkovikwGzSM6y4$)JJ`R0OX=7ZL^N5JK(}8w;DR~k>P7xx>iY_gs zBc``fe#)HIKwdmab8 zlu*f-Au_U?$n+$!rc%8$3=}VSfLZr&!(mY8x9^>O;ZqMh{|?}Et4p=kMEGJ$!Z%HB z7R}Nr;py{4Hgm-=-z7x$EA|@BS@t(}*)adQ2@STzX&myvRhBB>9n|Dnc1dS<^fG&c zy-RtTwNX?ya-}J+#zmS`VEEC-wxUU1>^!h5@D{vexnE=7h3HvGQPkMmxXdSNauJ5G zI$CXLCPw4ztX*ybbo%v&&b{>J!@oaGOQR#`2&|RNI+`{`MitSWO9Rz~ z@xONa-1Ld3o>Qhd(6MPY>LT;gV{HH^rc_)HLuY8nQ7N3}P8Qm%u}Jc zMh`?VW_!Nc2;ioY%AFG4Ta;ps?tTOzobZq0^KXSa|c*vk6_u9ILh;F|<|UAJ_G7Ude%voUrN8X1Q9FT=db zzGs+MaEsPUo!a*6^^Y86;k6x97T<={7L{^N-ces$z`5@iH1jGIf;QXLsc2bto@-$< z)Sw4gZTU-p?N%Fis`Tb%$PJ~g)cY>;s$ssxuAwn`d1KvT=kzkh{$b-AWA~tSpZq2v zzu`>HMPiZqhF5=A-p3Z7dzoxysyBbLOW|OX%)?`c^<~ zA$LcY9Q3&8O(1H4U6NHl!&cZ8k9~^`QIF&SI!jLs^QDb)vF8h@u8kMX)%A?deu?UO ziCrP14t7NB3T-N=uoPE=5UbN6n^tzdsG0vX%y;zT(`?+z96ZqWqne8DRa_vUt<%5# d$PeCLk>{WM(3DM&*0b*dC0B#JUN(+T{~rpg98v%P literal 0 HcmV?d00001 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml new file mode 100644 index 00000000..2f9ecb73 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml @@ -0,0 +1,26 @@ + + + + + 9 + + 10 + org.jenkinsci.plugins.workflow.steps.SleepStep + + + + + + time + 30 + + + + true + + + 1722980005296 + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml new file mode 100644 index 00000000..213232ad --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml @@ -0,0 +1,12 @@ + + + + + 2 + + + + 1722979974868 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml new file mode 100644 index 00000000..ff65b2ee --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml @@ -0,0 +1,26 @@ + + + + + 2 + + 3 + org.jenkinsci.plugins.workflow.support.steps.StageStep + + + + + + + name + stage + + + + true + + + 1722979974972 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml new file mode 100644 index 00000000..0f422ed2 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml @@ -0,0 +1,19 @@ + + + + + 3 + + 4 + org.jenkinsci.plugins.workflow.support.steps.StageStep + + + + + stage + + + 1722979974988 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml new file mode 100644 index 00000000..5dcd2edd --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml @@ -0,0 +1,26 @@ + + + + + 4 + + 5 + org.jenkinsci.plugins.workflow.steps.SleepStep + + + + + + time + 30 + + + + true + + + 1722979975077 + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml new file mode 100644 index 00000000..967f277a --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml @@ -0,0 +1,16 @@ + + + + + 5 + + 6 + 4 + + + + + 1722980005117 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml new file mode 100644 index 00000000..3625f1ef --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml @@ -0,0 +1,15 @@ + + + + + 6 + + 7 + 8 + + + + 1722980005177 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml new file mode 100644 index 00000000..ea60fd6d --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml @@ -0,0 +1,26 @@ + + + + + 7 + + 8 + org.jenkinsci.plugins.workflow.steps.TimeoutStep + + + + + + + time + 10 + + + + true + + + 1722980005227 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml new file mode 100644 index 00000000..a5767ab8 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml @@ -0,0 +1,16 @@ + + + + + 8 + + 9 + org.jenkinsci.plugins.workflow.steps.TimeoutStep + + + + + 1722980005244 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/legacyIds new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml new file mode 100644 index 00000000..826019f9 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml @@ -0,0 +1,11 @@ + + + false + + + + true + + + false + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber @@ -0,0 +1 @@ +2 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml new file mode 100644 index 00000000..56b066fd --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml @@ -0,0 +1,7 @@ + + + + test0 + 1 + + \ No newline at end of file From c6b4fa940e2289e124f88a1ffb929528b4ef202d Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Fri, 9 Aug 2024 17:15:45 -0400 Subject: [PATCH 2/2] Adjust implementation to attempt to resume any step that has a FlowNode --- .../workflow/flow/FlowExecutionList.java | 16 +++++----------- .../workflow/graph/StandardGraphLookupView.java | 2 +- .../LinearBlockHoppingScanner.java | 2 +- .../workflow/flow/FlowExecutionListTest.java | 15 +++++++++++++-- .../jobs/test0/builds/1/build.xml | 0 .../jobs/test0/builds/1/log | 0 .../jobs/test0/builds/1/log-index | 0 .../jobs/test0/builds/1/program.dat | Bin .../jobs/test0/builds/1/workflow/10.xml | 0 .../jobs/test0/builds/1/workflow/2.xml | 0 .../jobs/test0/builds/1/workflow/3.xml | 0 .../jobs/test0/builds/1/workflow/4.xml | 0 .../jobs/test0/builds/1/workflow/5.xml | 0 .../jobs/test0/builds/1/workflow/6.xml | 0 .../jobs/test0/builds/1/workflow/7.xml | 0 .../jobs/test0/builds/1/workflow/8.xml | 0 .../jobs/test0/builds/1/workflow/9.xml | 0 .../jobs/test0/builds/legacyIds | 0 .../jobs/test0/config.xml | 0 .../jobs/test0/nextBuildNumber | 0 ....plugins.workflow.flow.FlowExecutionList.xml | 0 21 files changed, 20 insertions(+), 15 deletions(-) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/build.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/log (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/log-index (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/program.dat (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/10.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/2.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/3.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/4.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/5.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/6.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/7.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/8.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/1/workflow/9.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/builds/legacyIds (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/config.xml (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/jobs/test0/nextBuildNumber (100%) rename src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/{resumeStepExecutionsWithCorruptFlowGraphWithLoop => resumeStepExecutionsWithCorruptFlowGraphWithCycle}/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml (100%) diff --git a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java index fe8effbf..a6203355 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java @@ -362,15 +362,14 @@ private static final class ParallelResumer { nodes.put(n, se); } else { LOGGER.warning(() -> "Could not find FlowNode for " + se + " so it will not be resumed"); - // TODO: Should we call se.getContext().onFailure()? } } catch (IOException | InterruptedException x) { LOGGER.log(Level.WARNING, "Could not look up FlowNode for " + se + " so it will not be resumed", x); - // TODO: Should we call se.getContext().onFailure()? } } - try { - for (FlowNode n : nodes.keySet()) { + for (Map.Entry entry : nodes.entrySet()) { + FlowNode n = entry.getKey(); + try { LinearBlockHoppingScanner scanner = new LinearBlockHoppingScanner(); scanner.setup(n); for (FlowNode parent : scanner) { @@ -379,14 +378,9 @@ private static final class ParallelResumer { break; } } + } catch (Exception x) { + LOGGER.log(Level.WARNING, x, () -> "Unable to compute enclosing blocks for " + n + ", so " + entry.getValue() + " might not resume successfully"); } - } catch (Exception e) { - // TODO: Should we instead just try to resume steps with no respect to topological order? - // There is something wrong with the FlowGraph. Kill all of the steps so the build fails instead of hanging forever. - for (StepExecution se : executions) { - se.getContext().onFailure(e); - } - onCompletion.run(); } } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java index 8f4f0f04..98de089c 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/graph/StandardGraphLookupView.java @@ -124,7 +124,7 @@ BlockStartNode bruteForceScanForEnclosingBlock(@NonNull final FlowNode node) { Set visited = new HashSet<>(); while (!(current instanceof FlowStartNode)) { // Hunt back for enclosing blocks, a potentially expensive operation if (!visited.add(current.getId())) { - throw new IllegalStateException("Loop in flow graph for " + node.getExecution() + " involving " + current); + throw new IllegalStateException("Cycle in flow graph for " + node.getExecution() + " involving " + current); } if (current instanceof BlockEndNode) { // Hop over the block to the start diff --git a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java index 3e4209dd..73720a4e 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/LinearBlockHoppingScanner.java @@ -91,7 +91,7 @@ protected FlowNode jumpBlockScan(@CheckForNull FlowNode node, @NonNull Collectio // Find the first candidate node preceding a block... and filtering by blacklist while (candidate instanceof BlockEndNode) { if (!visited.add(candidate.getId())) { - throw new IllegalStateException("Loop in flow graph for " + candidate.getExecution() + " involving " + candidate); + throw new IllegalStateException("Cycle in flow graph for " + candidate.getExecution() + " involving " + candidate); } candidate = ((BlockEndNode) candidate).getStartNode(); if (blacklistNodes.contains(candidate)) { diff --git a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java index 47a15707..5981d714 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -41,9 +41,12 @@ import java.time.Duration; import java.time.Instant; import java.util.Collections; +import java.util.Objects; import java.util.Set; import java.util.function.Supplier; import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.Collectors; import org.hamcrest.Matcher; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -162,7 +165,7 @@ public class FlowExecutionListTest { } @LocalData - @Test public void resumeStepExecutionsWithCorruptFlowGraphWithLoop() throws Throwable { + @Test public void resumeStepExecutionsWithCorruptFlowGraphWithCycle() throws Throwable { // LocalData created using the following snippet while the build was waiting in the _second_ sleep, except // for build.xml, which was captured during the sleep step. The StepEndNode for the stage was then adjusted to // have its startId point to the timeout step's StepStartNode, creating a loop. @@ -175,10 +178,18 @@ public class FlowExecutionListTest { r.waitForCompletion(b); }); */ + logging.capture(50); sessions.then(r -> { var p = r.jenkins.getItemByFullName("test0", WorkflowJob.class); var b = p.getBuildByNumber(1); - r.assertBuildStatus(Result.FAILURE, r.waitForCompletion(b)); + r.waitForCompletion(b); + assertThat(logging.getMessages(), hasItem(containsString("Unable to compute enclosing blocks"))); + var loggedExceptions = logging.getRecords().stream() + .map(LogRecord::getThrown) + .filter(Objects::nonNull) + .map(Throwable::toString) + .collect(Collectors.toList()); + assertThat(loggedExceptions, hasItem(containsString("Cycle in flow graph"))); }); } diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/build.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/build.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/build.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/log similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/log diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/log-index similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/log-index rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/log-index diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/program.dat similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/program.dat rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/program.dat diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/10.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/10.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/10.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/2.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/2.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/2.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/3.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/3.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/3.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/4.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/4.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/4.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/5.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/5.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/5.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/6.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/6.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/6.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/7.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/7.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/7.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/8.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/8.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/8.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/9.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/1/workflow/9.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/1/workflow/9.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/legacyIds similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/builds/legacyIds rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/builds/legacyIds diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/config.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/config.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/config.xml diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/nextBuildNumber similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/jobs/test0/nextBuildNumber rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/jobs/test0/nextBuildNumber diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml similarity index 100% rename from src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithLoop/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml rename to src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/resumeStepExecutionsWithCorruptFlowGraphWithCycle/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml