4

I use Jenkins to execute tests whenever a PR is created or updated.

The tests take about 20 minutes, and quite often a PR is created, but 4-5 minutes later is updated by another commit. In that case, I want to cancel the previous test run as it is no longer valuable.

I tried implementing it using a groovy script

import hudson.model.Result
import jenkins.model.CauseOfInterruption
import hudson.model.ParametersAction
import hudson.model.Job
import jenkins.model.Jenkins

//iterate through current project runs
build.getProject()._getRuns().iterator().each{ run ->
  def exec = run.getExecutor()
  //if the run is not a current build and it has executor (running) then stop it
  if(run != build && exec != null) {
    def other_run_params = run.actions.find{ it instanceof ParametersAction }?.parameters
    def current_build_params = build.actions.find{ it instanceof ParametersAction }?.parameters

    def should_cancel = true
    
    other_run_params.each { other_run_param ->
        current_build_params.each { current_build_param ->
            if (other_run_param.name == current_build_param.name && other_run_param.dump() != current_build_param.dump()) {
                should_cancel = false
            }
        }
    }
    
    // Cancelling all subprojects. If I skip this loop and just cancel 'exec', the subprojects continue to run
    if (should_cancel) {
        println("Attempting to cancel previous build!")
        // prepare the cause of interruption
        def cause = { "interrupted by build #${build.getId()}" as String } as CauseOfInterruption         

        // Cancel all child objects
        def buildingJobs = Jenkins.instance.getAllItems(Job.class).findAll { it.isBuilding() }
        buildingJobs.each { job ->
          allRuns = job._getRuns().iterator().each { child_run ->
            upstream = child_run.getCause(hudson.model.Cause.UpstreamCause.class)?.upstreamRun

            while (upstream != null) {
              if (upstream == run) {                
                def child_exec = child_run.getExecutor()
                if (child_exec != null) {
                  println("Cancelling child job!")
                  child_exec.interrupt(Result.ABORTED, cause)
                }
              }

              upstream = upstream.getCause(hudson.model.Cause.UpstreamCause.class)?.upstreamRun
            }
          }
        }

        // Cancel the root
        exec.interrupt(Result.ABORTED, cause)
    }
  }
}

But currently, I have in Jenkins about 30 jobs, each one with a history of about 5k, so this execution takes about 10 minutes to run. Can I do it more efficiently?

EDIT: If I just skip the second loop and just abort the second run, it does not stop all subprojects.

user972014
  • 3,296
  • 6
  • 49
  • 89
  • does it mean that interruption of 30 jobs takes 10 min? how many of them are running at time of interruption? could you log job name, status, and interruption time for each? maybe there are several that takes too long. history 5K - why not to make it shorter? not sure it's a good idea but probably you could start interruption in parallel. – daggett Jul 14 '20 at 14:33
  • The traversal is the part that takes so much time. not the actual interruption itself. Very few jobs are executed simutenously – user972014 Jul 14 '20 at 15:08
  • 1
    maybe you don't need to run through all `runs` `build.getProject()._getRuns().iterator().each{ run ->`. probably you need just one that older then current. if i understand correctly - you need just one. you could use `project.getNearestOldBuild(build.number)`. or just iterate through last 5 (for example)... – daggett Jul 14 '20 at 15:42
  • 1
    Really curious why you keep those 5k runs in the history, its really hogging your resources and it gets you to an amount where you should watch the available inodes on your storage. If currently there is no way to go through the currently building child jobs without looping through all child jobs (all 5k) then could have to have your jobs register themselves as running and remove them when finishing, i.e. in some globally aviable file, maybe [this](https://stackoverflow.com/questions/29674267/jenkins-persistent-editable-global-variable) gives a hint how to achieve this. – Dominik Gebhart Jul 17 '20 at 00:01
  • Its great you have reached this far, I also think you are trying to build a new feature which *does exist* on other tools like `Bamboo CI`. I would like you to review these links before progressing further: `https://stackoverflow.com/questions/40760716/jenkins-abort-running-build-if-new-one-is-started/44326216` `https://issues.jenkins-ci.org/browse/JENKINS-43353` – mdabdullah Jul 19 '20 at 17:14
  • @user972014 Traversing through 5000 jobs each time is a risk as it has a potential to crash your `master`. – mdabdullah Jul 19 '20 at 18:04

0 Answers0