• Icon: New Feature New Feature
    • Resolution: Fixed
    • Icon: Major Major
    • workflow-job-plugin
    • None
    • 2.42

      It is sometimes desirable for a job (such as a branch project) to simply abort any previously running builds as soon as a new build starts. For example, in a branch project for a pull request, you might want to see test results from an earlier commit even after pushing follow-up commits, but most of the time you only care about the results of the PR head, and computer time might be too valuable to waste on the older ones.

      (I think gerrit-trigger does something like this automatically, and I have seen grayaii invent the same kind of pattern with JenkinsPy.)

      Merely setting the job to not be concurrent-capable does not suffice, since then newer builds will queue up waiting for the older ones to finish.


      Manual equivalent courtesy of bsquizz:

      def buildNumber = BUILD_NUMBER as int; if (buildNumber > 1) milestone(buildNumber - 1); milestone(buildNumber)
      

          [JENKINS-43353] Ability to abort all previous running builds

          Jesse Glick added a comment -

          Might be considered a duplicate of JENKINS-40936, though the use cases are a little different: this RFE applies even to jobs with no manual approval step, and no need for interior milestones of any kind.

          Jesse Glick added a comment - Might be considered a duplicate of  JENKINS-40936 , though the use cases are a little different: this RFE applies even to jobs with no manual approval step, and no need for interior milestones of any kind.

          Jesse Glick added a comment -

          While something like an eager flag would be perfect, I am filing a PR showing a workaround: start your Jenkinsfile with

          for (int i = 0; i < (BUILD_NUMBER as int); i++) {milestone()}
          

          Jesse Glick added a comment - While something like an eager flag would be perfect, I am filing a PR showing a workaround: start your Jenkinsfile with for ( int i = 0; i < (BUILD_NUMBER as int ); i++) {milestone()}

          This is another option:

          @NonCPS
          void cancelPreviousRunningBuilds(int maxBuildsToSearch = 20) {
              RunWrapper b = currentBuild
              for (int i=0; i<maxBuildsToSearch; i++) {
                  b = b.getPreviousBuild();
                  if (b == null) break;
                  Run<?,?> rawBuild = b.rawBuild
                  if (rawBuild.isBuilding()) {
                      rawBuild.doStop()
                  }
              }
          }
          

          Just call cancelPreviousRunningBuilds() at the very fist line of the pipeline.

          Antonio Muñiz added a comment - This is another option: @NonCPS void cancelPreviousRunningBuilds( int maxBuildsToSearch = 20) { RunWrapper b = currentBuild for ( int i=0; i<maxBuildsToSearch; i++) { b = b.getPreviousBuild(); if (b == null ) break ; Run<?,?> rawBuild = b.rawBuild if (rawBuild.isBuilding()) { rawBuild.doStop() } } } Just call cancelPreviousRunningBuilds() at the very fist line of the pipeline.

          Sam Van Oort added a comment -

          jglick This is confusingly worded – could it be rephrased as "abort previous running builds even after they've passed the milestone"?

          Sam Van Oort added a comment - jglick This is confusingly worded – could it be rephrased as "abort previous running builds even after they've passed the milestone"?

          Jesse Glick added a comment -

          Not necessarily. The milestone step is simply a tool which could be used to implement this. The use case is to abort all previous running builds. See my first comment.

          Jesse Glick added a comment - Not necessarily. The milestone step is simply a tool which could be used to implement this. The use case is to abort all previous running builds. See my first comment.

          When pushing new changes to a PR, it would be awesome if the multi branch had an option to automatically kill all previous running builds/queued builds for that branch so that only the latest gets queued/built. This way if a developer commits and pushes and then does the same again immediately afterwards we don't have two builds, especially if the builds take 20+ minutes each, and we don't care about the intermediate result.

          Bert JW Regeer added a comment - When pushing new changes to a PR, it would be awesome if the multi branch had an option to automatically kill all previous running builds/queued builds for that branch so that only the latest gets queued/built. This way if a developer commits and pushes and then does the same again immediately afterwards we don't have two builds, especially if the builds take 20+ minutes each, and we don't care about the intermediate result.

          Jesse Glick added a comment -

          Yes that is exactly the use case listed in this issue’s description.

          (Pipeline builds do not generally sit in the queue, pending JENKINS-39180.)

          Jesse Glick added a comment - Yes that is exactly the use case listed in this issue’s description. (Pipeline builds do not generally sit in the queue, pending JENKINS-39180 .)

          Nicholas Brown added a comment - This would form an effective solution for: https://issues.jenkins-ci.org/browse/JENKINS-47503 https://issues.jenkins-ci.org/browse/JENKINS-43132

          Brandon Squizzato added a comment - - edited

          jglick's solution worked well for me until recently, when passing milestones started to take awhile. Passing the milestones used to happen very quickly but now for whatever reason it takes a second or so to pass each milestone. When your build number is 100+, obviously this can cause a big delay trying to pass each milestone created in the loop.

          So I've started to go with an approach like this:

          def buildNumber = env.BUILD_NUMBER as int
          if (buildNumber > 1) milestone(buildNumber - 1)
          milestone(buildNumber)

          The result here would be:
          Build 1 runs and creates milestone 1
          While build 1 is running, build 2 fires. It has milestone 1 and milestone 2

          Since build 2 passes milestone 1, it causes build #1 to abort.

           

          Brandon Squizzato added a comment - - edited jglick 's solution worked well for me until recently, when passing milestones started to take awhile. Passing the milestones used to happen very quickly but now for whatever reason it takes a second or so to pass each milestone. When your build number is 100+, obviously this can cause a big delay trying to pass each milestone created in the loop. So I've started to go with an approach like this: def buildNumber = env.BUILD_NUMBER as int if (buildNumber > 1) milestone(buildNumber - 1) milestone(buildNumber) The result here would be: Build 1 runs and creates milestone 1 While build 1 is running, build 2 fires. It has milestone 1 and milestone 2 Since build 2 passes milestone 1, it causes build #1 to abort.  

          Jesse Glick added a comment -

          Yes, this step is not going to be efficient when there are a lot of milestones. I filed PR 19 to record your suggestion.

          Jesse Glick added a comment - Yes, this step is not going to be efficient when there are a lot of milestones. I filed PR 19 to record your suggestion.

          bsquizz thanks for that solution, we are up to build number 900 and passing all of the milestones while pretty fast is not the fastest, this should help

          Bert JW Regeer added a comment - bsquizz thanks for that solution, we are up to build number 900 and passing all of the milestones while pretty fast is not the fastest, this should help

          Mark Han added a comment -

          We should make sure this change will allow for QUEUED builds to cancel RUNNING builds. We are running into a scenario where the amount of executors is limited, and the build server is being super congested by running out-of-date PR builds that should ideally be cancelled by the incoming queued PR builds. 

          Mark Han added a comment - We should make sure this change will allow for QUEUED builds to cancel RUNNING builds. We are running into a scenario where the amount of executors is limited, and the build server is being super congested by running out-of-date PR builds that should ideally be cancelled by the incoming queued PR builds. 

          Jesse Glick added a comment -

          Pipeline builds are flyweight and normally exit the queue immediately; it is node blocks which would wait in queue. So just put the milestone dance at the top of the Jenkinsfile and you are fine. Working in jenkinsci/bom as far as I know.

          Jesse Glick added a comment - Pipeline builds are flyweight and normally exit the queue immediately; it is node blocks which would wait in queue. So just put the milestone dance at the top of the Jenkinsfile and you are fine. Working in jenkinsci/bom as far as I know.

          Mark Han added a comment - - edited

          jglick TIL about flyweight! This is helpful. In our recent past we have had issues in the past with milestones.

           

          Scenario: We have 7 PRs ready using Bitbucket, and 7 PR builds are queued up using the Multibranch Pipeline. After the first one of those gets merged, the other 6 have to rebuild because Develop was changed. However, a few of the previous runs are still trying to execute so a queue forms that looks something like this (Assuming there are multiple builds surrounding these that look similarly): 

          QUEUED JOBS:

          part of Android-Build-Name >> PR-2715 #8

          part of Android-Build-Name >> PR-2715 #7

          RUNNING JOBS:
          part of Android-Build-Name >> PR-2715 #6

          So, ideally we want to kill this running job #6 and the queued job #7 if a newer job #8 is queued up.

          Would something similar to line number 1 in the jenkinsfile for `jenkinsci/bom` solve this?

          Mark Han added a comment - - edited jglick  TIL about flyweight! This is helpful. In our recent past we have had issues in the past with milestones.   Scenario : We have 7 PRs ready using Bitbucket, and 7 PR builds are queued up using the Multibranch Pipeline. After the first one of those gets merged, the other 6 have to rebuild because Develop was changed. However, a few of the previous runs are still trying to execute so a queue forms that looks something like this (Assuming there are multiple builds surrounding these that look similarly):  QUEUED JOBS: part of Android-Build-Name >> PR-2715 #8 part of Android-Build-Name >> PR-2715 #7 RUNNING JOBS: part of Android-Build-Name >> PR-2715 #6 So, ideally we want to kill this running job #6 and the queued job #7 if a newer job #8 is queued up. Would something similar to line number 1 in the jenkinsfile for `jenkinsci/bom` solve this?

          Jesse Glick added a comment -

          Yes, it should. When build 8 starts, it should abort builds 6 and 7. When a build is aborted, in turn, it should abort any sh steps already running inside any node blocks, and also cancel any queue items corresponding to node blocks which have not yet started.

          Jesse Glick added a comment - Yes, it should. When build 8 starts, it should abort builds 6 and 7. When a build is aborted, in turn, it should abort any sh steps already running inside any node blocks, and also cancel any queue items corresponding to node blocks which have not yet started.

          Mark Han added a comment -

          Ended up not using Milestone because it doesn't communicate the build # that was stopped. Instead we're trying to use 

          def cancelPreviousBuilds() {
           // Check for other instances of this particular build, cancel any that are older than the current one
           def jobName = env.JOB_NAME
           def currentBuildNumber = env.BUILD_NUMBER.toInteger()
           def currentJob = Jenkins.instance.getItemByFullName(jobName)
          
           // Loop through all instances of this particular job/branch
           for (def build : currentJob.builds) {
           if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
           echo "Older build still queued. Sending kill signal to build number: ${build.number}"
           build.doStop()
           }
           }
          }

          If we put this outside of our node blocks, things are working well. We previously had this in a node{} which was horrible, and resolved the issue after you informing me of the heavyweight vs flyweight executors.

          Mark Han added a comment - Ended up not using Milestone because it doesn't communicate the build # that was stopped. Instead we're trying to use  def cancelPreviousBuilds() { // Check for other instances of this particular build, cancel any that are older than the current one def jobName = env.JOB_NAME def currentBuildNumber = env.BUILD_NUMBER.toInteger() def currentJob = Jenkins.instance.getItemByFullName(jobName) // Loop through all instances of this particular job/branch for (def build : currentJob.builds) { if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) { echo "Older build still queued. Sending kill signal to build number: ${build.number}" build.doStop() } } } If we put this outside of our node blocks, things are working well. We previously had this in a node{} which was horrible, and resolved the issue after you informing me of the heavyweight vs flyweight executors.

          bsquizz Not sure how your solution is supposed to work after the first build. Each subsequent build will create two milestones and pass them immediately. First build is the only one that will be aborted.

          Kamil Magomedov added a comment - bsquizz Not sure how your solution is supposed to work after the first build. Each subsequent build will create two milestones and pass them immediately. First build is the only one that will be aborted.

          kmagomedov

          Build #1 would define milestone(1)
          Build #2 would define milestone(1) and milestone(2)
          Build #3 would define milestone(2) and milestone(3)
          Build #4 would define milestone(3) and milestone(4)

          and so on ...

          Every time a build runs, it is passing the highest milestone of the previous build. This causes the previous build to abort. From https://jenkins.io/doc/pipeline/steps/pipeline-milestone-step/

          > The milestone step forces all builds to go through in order, so an older build will never be allowed pass a milestone (it is aborted) if a newer build already passed it. 

          Brandon Squizzato added a comment - kmagomedov Build #1 would define milestone(1) Build #2 would define milestone(1) and milestone(2) Build #3 would define milestone(2) and milestone(3) Build #4 would define milestone(3) and milestone(4) and so on ... Every time a build runs, it is passing the highest milestone of the previous build. This causes the previous build to abort. From https://jenkins.io/doc/pipeline/steps/pipeline-milestone-step/ – > The milestone step forces all builds to go through in order, so an older build will never be allowed pass a milestone (it is aborted) if a newer build already passed it. 

          Kamil Magomedov added a comment - - edited

          bsquizz ah, I apologize, I had the wrong idea about the way milestone works. I thought that for the older builds to be aborted the newest build has to pass at least one more milestone step. So three milestone steps for Build #3 and so on. That's how I always used it. Good to know that there's the other way, thank you!

          Kamil Magomedov added a comment - - edited bsquizz ah, I apologize, I had the wrong idea about the way milestone works. I thought that for the older builds to be aborted the newest build has to pass at least one more milestone step. So three milestone steps for Build #3 and so on. That's how I always used it. Good to know that there's the other way, thank you!

          Paul Eipper added a comment -

          How to use the milestone solution in a declarative pipeline ? bsquizz Do you have a sample Jenkinsfile doing this?

          Paul Eipper added a comment - How to use the milestone solution in a declarative pipeline ? bsquizz Do you have a sample Jenkinsfile doing this?

          Stanislav Ovchar added a comment - Created PR  https://github.com/jenkinsci/pipeline-milestone-step-plugin/pull/18

          Donald Morton added a comment - - edited

          This code caused 100% CPU on my Jenkins server:

          def buildNumber = env.BUILD_NUMBER as int
          if (buildNumber > 1) milestone(buildNumber - 1)
          milestone(buildNumber)
          

          I had restarted Jenkins after a security patch, and 20-30 builds kicked off from Branch Indexing. They all hung on the milestone() step. I finally started trying to kill them all, but then I noticed CPU was maxed out at 100% and it was taking forever. Had to comment out the above code and restart to fix it. 

          Attached a thread dump.

          Donald Morton added a comment - - edited This code caused 100% CPU on my Jenkins server: def buildNumber = env.BUILD_NUMBER as int if (buildNumber > 1) milestone(buildNumber - 1) milestone(buildNumber) I had restarted Jenkins after a security patch, and 20-30 builds kicked off from Branch Indexing. They all hung on the milestone() step. I finally started trying to kill them all, but then I noticed CPU was maxed out at 100% and it was taking forever. Had to comment out the above code and restart to fix it.  Attached a thread dump.

          Kyle McKnight added a comment -

          Would also like to know how to do this in declarative pipeline 

          Kyle McKnight added a comment - Would also like to know how to do this in declarative pipeline 

          Kyle McKnight added a comment -

          I haven't tried it yet, but lkraider, found this stackoverflow comment https://stackoverflow.com/a/52811034/1272355 for doing this with declarative pipeline

          Kyle McKnight added a comment - I haven't tried it yet, but lkraider , found this stackoverflow comment https://stackoverflow.com/a/52811034/1272355  for doing this with declarative pipeline

          Kalle Niemitalo added a comment - - edited

          workflow-job-plugin #200 added support for:

          • properties([disableConcurrentBuilds(abortPrevious: true)]) as a step in a scripted pipeline
          • options { disableConcurrentBuilds abortPrevious: true } as a directive in a declarative pipeline

          I have a multibranch declarative pipeline in which I want to abort previous builds of pull requests but allow concurrent builds of branches. This cannot be done with the options directive because, although the value of the abortPrevious parameter can be made conditional, the disableConcurrentBuilds option itself cannot. I tried using the properties step instead but got a message saying it is not allowed:

          org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
          WorkflowScript: 52: Invalid step "properties" used - not allowed in this context - The properties step cannot be used in Declarative Pipelines @ line 52, column 17.
          

          I then moved that into a script step, and that seems to be working fine, but I don't understand why it is even disallowed in the first place.

          The options { when { … } } construct that was suggested in JENKINS-41392 comment 344397 would make things easier.

          Kalle Niemitalo added a comment - - edited workflow-job-plugin #200 added support for: properties( [disableConcurrentBuilds(abortPrevious: true)] ) as a step in a scripted pipeline options { disableConcurrentBuilds abortPrevious: true } as a directive in a declarative pipeline I have a multibranch declarative pipeline in which I want to abort previous builds of pull requests but allow concurrent builds of branches. This cannot be done with the options directive because, although the value of the abortPrevious parameter can be made conditional, the disableConcurrentBuilds option itself cannot. I tried using the properties step instead but got a message saying it is not allowed: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: WorkflowScript: 52: Invalid step "properties" used - not allowed in this context - The properties step cannot be used in Declarative Pipelines @ line 52, column 17. I then moved that into a script step, and that seems to be working fine, but I don't understand why it is even disallowed in the first place. The options { when { … } } construct that was suggested in JENKINS-41392 comment 344397 would make things easier.

            jglick Jesse Glick
            jglick Jesse Glick
            Votes:
            31 Vote for this issue
            Watchers:
            42 Start watching this issue

              Created:
              Updated:
              Resolved: