From ce222c48b0ef41b33dfdc381bf226d81e616af3a Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 6 Aug 2024 17:52:10 -0400 Subject: [PATCH 1/6] Add ignored test for corrupt flow graph with a loop that lead to a memory leak --- .../workflow/flow/FlowExecutionListTest.java | 28 +++++++++++ .../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 +++ 18 files changed, 293 insertions(+) create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/program.dat create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml 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..3047ca6f 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -54,6 +54,7 @@ import org.jenkinsci.plugins.workflow.steps.StepExecution; import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.junit.Rule; import org.jvnet.hudson.test.BuildWatcher; @@ -61,6 +62,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 +162,32 @@ public class FlowExecutionListTest { }); } + @Ignore("Build never completes due to infinite loop") + @LocalData + @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() 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); + // TODO: Create and run a new build, call StepExecutionIterator.applyAll while it is running, and assert + // that we do not leak a hard reference to it through the task queue for the CpsVmExecutorService for the + // stuck build and the futures inside of StepExecutionIteratorImpl.applyAll. + // TODO: Ideally, we would detect the issue with the stuck build and find some way to kill it. + 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log new file mode 100644 index 00000000..f8c5883d --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml new file mode 100644 index 00000000..826019f9 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber @@ -0,0 +1 @@ +2 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml @@ -0,0 +1,7 @@ + + + + test0 + 1 + + \ No newline at end of file From 9b6c0aed76fd3f20347d459c20173d65636a6992 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Wed, 7 Aug 2024 18:23:27 -0400 Subject: [PATCH 2/6] Fix the memory leak by making StepExecutionIteratorImpl return Future> instead of Future> --- .../workflow/flow/FlowExecutionList.java | 32 +++++++++--------- .../workflow/flow/FlowExecutionListTest.java | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+), 15 deletions(-) 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..4a6c49a6 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java @@ -253,24 +253,26 @@ public ListenableFuture apply(final Function f) { for (FlowExecution e : FlowExecutionList.get()) { ListenableFuture> execs = e.getCurrentExecutions(false); - all.add(execs); - Futures.addCallback(execs,new FutureCallback>() { - @Override - public void onSuccess(@NonNull List result) { - for (StepExecution e : result) { - try { - f.apply(e); - } catch (RuntimeException x) { - LOGGER.log(Level.WARNING, null, x); - } + // We transform the futures that return List into futures that return Void before + // passing them to Futures.allAsList so that the combined future only holds strong references to each + // FlowExecution until its StepExecutions have been loaded and applied to the function. This prevents + // us from leaking references to all processed executions in the case where a single build has a stuck + // CpsVmExecutorService that prevents the future returned by getCurrentExecutions from completing. + ListenableFuture results = Futures.transform(execs, (List result) -> { + for (StepExecution se : result) { + try { + f.apply(se); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); } } - - @Override - public void onFailure(@NonNull Throwable t) { - LOGGER.log(Level.WARNING, null, t); - } + return null; + }, MoreExecutors.directExecutor()); + ListenableFuture resultsWithWarningsLogged = Futures.catching(results, Throwable.class, t -> { + LOGGER.log(Level.WARNING, null, t); + return null; }, MoreExecutors.directExecutor()); + all.add(resultsWithWarningsLogged); } return Futures.allAsList(all); 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 3047ca6f..d52dc8a8 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -38,12 +38,14 @@ import hudson.model.TaskListener; import hudson.model.queue.QueueTaskFuture; import java.io.Serializable; +import java.lang.ref.WeakReference; import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.Set; import java.util.function.Supplier; import java.util.logging.Level; +import jenkins.model.Jenkins; import org.hamcrest.Matcher; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -61,6 +63,7 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.LoggerRule; import org.jvnet.hudson.test.JenkinsSessionRule; +import org.jvnet.hudson.test.MemoryAssert; import org.jvnet.hudson.test.TestExtension; import org.jvnet.hudson.test.recipes.LocalData; import org.kohsuke.stapler.DataBoundConstructor; @@ -188,6 +191,36 @@ public class FlowExecutionListTest { }); } + @Ignore("MemoryAssert is unable to clear the SoftReference from AbstractLazyLoadRunMap. Heap dumps show a strong reference from the test execution thread as a local variable, which is not captured by MemoryAssert. Also needs to be moved to a class with no BuildWatcher.") + @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck2() throws Throwable { + sessions.then(r -> { + WeakReference buildRef; + { + var stuck = r.createProject(WorkflowJob.class, "stuck"); + stuck.setDefinition(new CpsFlowDefinition("parallel(one: { echo 'test message'; Thread.sleep(Integer.MAX_VALUE); })", false)); + var stuckBuild = stuck.scheduleBuild2(0).waitForStart(); + + var notStuck = r.createProject(WorkflowJob.class, "not-stuck"); + notStuck.setDefinition(new CpsFlowDefinition("semaphore('wait')", true)); + var notStuckBuild = notStuck.scheduleBuild2(0).waitForStart(); + buildRef = new WeakReference<>(notStuckBuild); + + SemaphoreStep.waitForStart("wait/1", notStuckBuild); + r.waitForMessage("test message", stuckBuild); + Thread.sleep(1000); // We need Thread.sleep to be running in the CpsVmExecutorService. + + // Make FlowExecutionList$StepExecutionIteratorImpl.applyAll submit a task to the CpsVmExecutorService + // for stuck #1 that will never complete, so the resulting future will never complete. + var future = StepExecution.applyAll(e -> null); + + SemaphoreStep.success("wait/1", null); + r.waitForCompletion(notStuckBuild); + Jenkins.get().getQueue().clearLeftItems(); // Clean up the soft reference immediately. + } + MemoryAssert.assertGC(buildRef, true); + }); + } + public static class NonResumableStep extends Step implements Serializable { public static final long serialVersionUID = 1L; @DataBoundConstructor From b2c93a4ac1a2f8d957f440a4704924204c1ad8ef Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Wed, 7 Aug 2024 19:11:58 -0400 Subject: [PATCH 3/6] Unignore MemoryAssert test --- .../workflow/flow/FlowExecutionListTest.java | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) 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 d52dc8a8..2bb34485 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -167,7 +167,7 @@ public class FlowExecutionListTest { @Ignore("Build never completes due to infinite loop") @LocalData - @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() throws Throwable { + @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuckInfiniteLoop() 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. @@ -191,33 +191,29 @@ public class FlowExecutionListTest { }); } - @Ignore("MemoryAssert is unable to clear the SoftReference from AbstractLazyLoadRunMap. Heap dumps show a strong reference from the test execution thread as a local variable, which is not captured by MemoryAssert. Also needs to be moved to a class with no BuildWatcher.") - @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck2() throws Throwable { + @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() throws Throwable { sessions.then(r -> { - WeakReference buildRef; - { - var stuck = r.createProject(WorkflowJob.class, "stuck"); - stuck.setDefinition(new CpsFlowDefinition("parallel(one: { echo 'test message'; Thread.sleep(Integer.MAX_VALUE); })", false)); - var stuckBuild = stuck.scheduleBuild2(0).waitForStart(); - - var notStuck = r.createProject(WorkflowJob.class, "not-stuck"); - notStuck.setDefinition(new CpsFlowDefinition("semaphore('wait')", true)); - var notStuckBuild = notStuck.scheduleBuild2(0).waitForStart(); - buildRef = new WeakReference<>(notStuckBuild); - - SemaphoreStep.waitForStart("wait/1", notStuckBuild); - r.waitForMessage("test message", stuckBuild); - Thread.sleep(1000); // We need Thread.sleep to be running in the CpsVmExecutorService. - - // Make FlowExecutionList$StepExecutionIteratorImpl.applyAll submit a task to the CpsVmExecutorService - // for stuck #1 that will never complete, so the resulting future will never complete. - var future = StepExecution.applyAll(e -> null); - - SemaphoreStep.success("wait/1", null); - r.waitForCompletion(notStuckBuild); - Jenkins.get().getQueue().clearLeftItems(); // Clean up the soft reference immediately. - } - MemoryAssert.assertGC(buildRef, true); + var notStuck = r.createProject(WorkflowJob.class, "not-stuck"); + notStuck.setDefinition(new CpsFlowDefinition("semaphore('wait')", true)); + var notStuckBuild = notStuck.scheduleBuild2(0).waitForStart(); + SemaphoreStep.waitForStart("wait/1", notStuckBuild); + WeakReference notStuckBuildRef = new WeakReference<>(notStuckBuild); + // Create a Pipeline that runs a long-lived task on its CpsVmExecutorService, causing it to get stuck. + var stuck = r.createProject(WorkflowJob.class, "stuck"); + stuck.setDefinition(new CpsFlowDefinition("parallel(one: { echo 'test message'; Thread.sleep(Integer.MAX_VALUE); })", false)); + var stuckBuild = stuck.scheduleBuild2(0).waitForStart(); + r.waitForMessage("test message", stuckBuild); + Thread.sleep(1000); // We need Thread.sleep to be running in the CpsVmExecutorService. + // Make FlowExecutionList$StepExecutionIteratorImpl.applyAll submit a task to the CpsVmExecutorService + // for stuck #1 that will never complete, so the resulting future will never complete. + StepExecution.applyAll(e -> null); + // Let notStuckBuild complete and check that it can be GC'd. + SemaphoreStep.success("wait/1", null); + r.waitForCompletion(notStuckBuild); + notStuckBuild = null; // Clear out the local variable in this thread. + Jenkins.get().getQueue().clearLeftItems(); // We don't want to wait 5 minutes. + MemoryAssert.assertGC(notStuckBuildRef, true); + // TODO: Test cleanup hangs for 1 minute in CpsFlowExecution.suspendAll because the checkpoint task can't run. }); } From 71006111e7df33fbcc0cde1e8a81569127953492 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Thu, 8 Aug 2024 17:52:27 -0400 Subject: [PATCH 4/6] Remove infinite loop test (it is included in a separate newly filed PR) and simplify memory leak Pipeline --- .../workflow/flow/FlowExecutionListTest.java | 30 +----------- .../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 5265 -> 0 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 --- 18 files changed, 1 insertion(+), 294 deletions(-) delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/program.dat delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber delete mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml 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 2bb34485..86309d01 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -56,7 +56,6 @@ import org.jenkinsci.plugins.workflow.steps.StepExecution; import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; import org.junit.ClassRule; -import org.junit.Ignore; import org.junit.Test; import org.junit.Rule; import org.jvnet.hudson.test.BuildWatcher; @@ -65,7 +64,6 @@ import org.jvnet.hudson.test.JenkinsSessionRule; import org.jvnet.hudson.test.MemoryAssert; import org.jvnet.hudson.test.TestExtension; -import org.jvnet.hudson.test.recipes.LocalData; import org.kohsuke.stapler.DataBoundConstructor; public class FlowExecutionListTest { @@ -165,32 +163,6 @@ public class FlowExecutionListTest { }); } - @Ignore("Build never completes due to infinite loop") - @LocalData - @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuckInfiniteLoop() 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); - // TODO: Create and run a new build, call StepExecutionIterator.applyAll while it is running, and assert - // that we do not leak a hard reference to it through the task queue for the CpsVmExecutorService for the - // stuck build and the futures inside of StepExecutionIteratorImpl.applyAll. - // TODO: Ideally, we would detect the issue with the stuck build and find some way to kill it. - r.waitForCompletion(b); - }); - } - @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() throws Throwable { sessions.then(r -> { var notStuck = r.createProject(WorkflowJob.class, "not-stuck"); @@ -200,7 +172,7 @@ public class FlowExecutionListTest { WeakReference notStuckBuildRef = new WeakReference<>(notStuckBuild); // Create a Pipeline that runs a long-lived task on its CpsVmExecutorService, causing it to get stuck. var stuck = r.createProject(WorkflowJob.class, "stuck"); - stuck.setDefinition(new CpsFlowDefinition("parallel(one: { echo 'test message'; Thread.sleep(Integer.MAX_VALUE); })", false)); + stuck.setDefinition(new CpsFlowDefinition("echo 'test message'; Thread.sleep(Integer.MAX_VALUE)", false)); var stuckBuild = stuck.scheduleBuild2(0).waitForStart(); r.waitForMessage("test message", stuckBuild); Thread.sleep(1000); // We need Thread.sleep to be running in the CpsVmExecutorService. diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml deleted file mode 100644 index d2b7e367..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/build.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log deleted file mode 100644 index f8c5883d..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log +++ /dev/null @@ -1,13 +0,0 @@ -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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index deleted file mode 100644 index bfafbf78..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/log-index +++ /dev/null @@ -1,5 +0,0 @@ -1232 5 -1252 -2157 8 -2189 -2789 10 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/program.dat deleted file mode 100644 index aeae086b3dd4f5e7c9aba3ba7f60a06962c2eb97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml deleted file mode 100644 index 2f9ecb73..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/10.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml deleted file mode 100644 index 213232ad..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/2.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - 2 - - - - 1722979974868 - - - \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml deleted file mode 100644 index ff65b2ee..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/3.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml deleted file mode 100644 index 0f422ed2..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/4.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml deleted file mode 100644 index 5dcd2edd..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/5.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml deleted file mode 100644 index 967f277a..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/6.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 5 - - 6 - 4 - - - - - 1722980005117 - - - \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml deleted file mode 100644 index 3625f1ef..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/7.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - 6 - - 7 - 8 - - - - 1722980005177 - - - \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml deleted file mode 100644 index ea60fd6d..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/8.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml deleted file mode 100644 index a5767ab8..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/1/workflow/9.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 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/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/builds/legacyIds deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml deleted file mode 100644 index 826019f9..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/config.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - false - - - - true - - - false - \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber deleted file mode 100644 index 0cfbf088..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/jobs/test0/nextBuildNumber +++ /dev/null @@ -1 +0,0 @@ -2 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml deleted file mode 100644 index 56b066fd..00000000 --- a/src/test/resources/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest/stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - test0 - 1 - - \ No newline at end of file From 2de670dda3e6bdfa207e6c2b5bbcb2b56129a239 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Fri, 9 Aug 2024 13:07:46 -0400 Subject: [PATCH 5/6] Allow the stuck build to be cleaned up promptly at the end of the test --- pom.xml | 6 ++ .../workflow/flow/FlowExecutionList.java | 9 +- .../workflow/flow/FlowExecutionListTest.java | 84 +++++++++++++++---- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 81360cb8..9e74e5a0 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,12 @@ org.jenkins-ci.plugins scm-api + + org.awaitility + awaitility + 4.2.2 + test + org.jenkins-ci.plugins.workflow workflow-job 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 4a6c49a6..1aea2847 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionList.java @@ -253,11 +253,10 @@ public ListenableFuture apply(final Function f) { for (FlowExecution e : FlowExecutionList.get()) { ListenableFuture> execs = e.getCurrentExecutions(false); - // We transform the futures that return List into futures that return Void before - // passing them to Futures.allAsList so that the combined future only holds strong references to each - // FlowExecution until its StepExecutions have been loaded and applied to the function. This prevents - // us from leaking references to all processed executions in the case where a single build has a stuck - // CpsVmExecutorService that prevents the future returned by getCurrentExecutions from completing. + // It is important that the combined future's return values do not reference the individual step + // executions, so we use transform instead of addCallback. Otherwise, it is possible to leak references + // to the WorkflowRun for each processed StepExecution in the case where a single live FlowExecution + // has a stuck CpsVmExecutorService that prevents the getCurrentExecutions future from completing. ListenableFuture results = Futures.transform(execs, (List result) -> { for (StepExecution se : result) { try { 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 86309d01..37acb052 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -24,6 +24,7 @@ package org.jenkinsci.plugins.workflow.flow; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; @@ -39,14 +40,13 @@ import hudson.model.queue.QueueTaskFuture; import java.io.Serializable; import java.lang.ref.WeakReference; -import java.time.Duration; -import java.time.Instant; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Set; -import java.util.function.Supplier; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import jenkins.model.Jenkins; -import org.hamcrest.Matcher; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -54,6 +54,7 @@ import org.jenkinsci.plugins.workflow.steps.StepContext; import org.jenkinsci.plugins.workflow.steps.StepDescriptor; import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.jenkinsci.plugins.workflow.steps.StepExecutions; import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; import org.junit.ClassRule; import org.junit.Test; @@ -135,7 +136,7 @@ public class FlowExecutionListTest { at org.jenkinsci.plugins.workflow.flow.FlowExecutionList$ItemListenerImpl.onLoaded(FlowExecutionList.java:175) at jenkins.model.Jenkins.(Jenkins.java:1019) */ - waitFor(logging::getMessages, hasItem(containsString("Will resume [org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep"))); + await().atMost(5, TimeUnit.SECONDS).until(logging::getMessages, hasItem(containsString("Will resume [org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep"))); WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class); SemaphoreStep.success("wait/1", null); WorkflowRun b = p.getBuildByNumber(1); @@ -166,26 +167,28 @@ public class FlowExecutionListTest { @Test public void stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck() throws Throwable { sessions.then(r -> { var notStuck = r.createProject(WorkflowJob.class, "not-stuck"); - notStuck.setDefinition(new CpsFlowDefinition("semaphore('wait')", true)); + notStuck.setDefinition(new CpsFlowDefinition("semaphore 'wait'", true)); var notStuckBuild = notStuck.scheduleBuild2(0).waitForStart(); SemaphoreStep.waitForStart("wait/1", notStuckBuild); WeakReference notStuckBuildRef = new WeakReference<>(notStuckBuild); // Create a Pipeline that runs a long-lived task on its CpsVmExecutorService, causing it to get stuck. var stuck = r.createProject(WorkflowJob.class, "stuck"); - stuck.setDefinition(new CpsFlowDefinition("echo 'test message'; Thread.sleep(Integer.MAX_VALUE)", false)); + stuck.setDefinition(new CpsFlowDefinition("blockSynchronously 'stuck'", false)); var stuckBuild = stuck.scheduleBuild2(0).waitForStart(); - r.waitForMessage("test message", stuckBuild); - Thread.sleep(1000); // We need Thread.sleep to be running in the CpsVmExecutorService. + await().atMost(5, TimeUnit.SECONDS).until(() -> SynchronousBlockingStep.isStarted("stuck")); // Make FlowExecutionList$StepExecutionIteratorImpl.applyAll submit a task to the CpsVmExecutorService // for stuck #1 that will never complete, so the resulting future will never complete. StepExecution.applyAll(e -> null); - // Let notStuckBuild complete and check that it can be GC'd. + // Let notStuckBuild complete and clean up all references. SemaphoreStep.success("wait/1", null); r.waitForCompletion(notStuckBuild); notStuckBuild = null; // Clear out the local variable in this thread. - Jenkins.get().getQueue().clearLeftItems(); // We don't want to wait 5 minutes. + Jenkins.get().getQueue().clearLeftItems(); // Otherwise we'd have to wait 5 minutes for the cache to be cleared. + // Make sure that the reference can be GC'd. MemoryAssert.assertGC(notStuckBuildRef, true); - // TODO: Test cleanup hangs for 1 minute in CpsFlowExecution.suspendAll because the checkpoint task can't run. + // Allow stuck #1 to complete so the test can be cleaned up promptly. + SynchronousBlockingStep.unblock("stuck"); + r.waitForCompletion(stuckBuild); }); } @@ -227,14 +230,59 @@ public String getFunctionName() { } /** - * Wait up to 5 seconds for the given supplier to return a matching value. + * Blocks the CPS VM thread synchronously (bad!) to test related problems. */ - private static void waitFor(Supplier valueSupplier, Matcher matcher) throws InterruptedException { - Instant end = Instant.now().plus(Duration.ofSeconds(5)); - while (!matcher.matches(valueSupplier.get()) && Instant.now().isBefore(end)) { - Thread.sleep(100L); + public static class SynchronousBlockingStep extends Step implements Serializable { + private static final long serialVersionUID = 1L; + private static final Map blocked = new HashMap<>(); + private final String id; + + @DataBoundConstructor + public SynchronousBlockingStep(String id) { + this.id = id; + if (blocked.put(id, State.NOT_STARTED) != null) { + throw new IllegalArgumentException("Attempting to reuse ID: " + id); + } + } + + @Override + public StepExecution start(StepContext context) throws Exception { + return StepExecutions.synchronous(context, c -> { + blocked.put(id, State.BLOCKED); + c.get(TaskListener.class).getLogger().println(id + " blocked"); + while (blocked.get(id) == State.BLOCKED) { + Thread.sleep(100L); + } + c.get(TaskListener.class).getLogger().println(id + " unblocked "); + return null; + }); + } + + public static boolean isStarted(String id) { + var state = blocked.get(id); + return state != null && state != State.NOT_STARTED; + } + + public static void unblock(String id) { + blocked.put(id, State.UNBLOCKED); + } + + private enum State { + NOT_STARTED, + BLOCKED, + UNBLOCKED, + } + + @TestExtension public static class DescriptorImpl extends StepDescriptor { + @Override + public Set> getRequiredContext() { + return Collections.singleton(TaskListener.class); + } + @Override + public String getFunctionName() { + return "blockSynchronously"; + } } - assertThat("Matcher should have matched after 5s", valueSupplier.get(), matcher); } } From b15fde4c498cb539c127dda3300af390c33ddfde Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Fri, 9 Aug 2024 14:30:25 -0400 Subject: [PATCH 6/6] Only activate TestExtension for blockSynchronously in the relevant test method Co-authored-by: Jesse Glick --- .../jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 37acb052..9b9ee501 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/flow/FlowExecutionListTest.java @@ -273,7 +273,7 @@ private enum State { UNBLOCKED, } - @TestExtension public static class DescriptorImpl extends StepDescriptor { + @TestExtension("stepExecutionIteratorDoesNotLeakBuildsWhenOneIsStuck") public static class DescriptorImpl extends StepDescriptor { @Override public Set> getRequiredContext() { return Collections.singleton(TaskListener.class);