91

I do not want to allow two jobs of the same type (same repository) to run in parallel on the same node.

How can I do this using groovy inside Jenkinsfile ?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
sorin
  • 161,544
  • 178
  • 535
  • 806

12 Answers12

84

The answer provided in https://stackoverflow.com/a/43963315/6839445 is deprecated.

The current method to disable concurrent builds is to set options:

options { disableConcurrentBuilds() }

Detailed description is available here: https://jenkins.io/doc/book/pipeline/syntax/#options

M0nt3c1t0
  • 1,016
  • 7
  • 6
  • 16
    I'm trying this and it disables concurrent builds globally, OP wants to disable concurrent builds per node but still allow more than one build to occur at the same time, just one per available node. – Quantic Feb 28 '18 at 21:56
  • This is working for our particular purpose, although it bothers me that we have to configure this in the pipeline and that it can't just be done globally somehow. – Hakanai May 09 '19 at 23:06
51

You got at the disableConcurrentBuilds property:

properties properties: [
  ...
  disableConcurrentBuilds(),
  ...
]

Then the job would wait the older one to finish first

z7sg Ѫ
  • 3,153
  • 1
  • 23
  • 35
hypery2k
  • 1,681
  • 15
  • 20
40

Another way is to use the Lockable Resources plugin: https://wiki.jenkins-ci.org/display/JENKINS/Lockable+Resources+Plugin

You can define locks (mutexes) however you want and can put variables in the names. E.g. to prevent multiple jobs from using a compiler concurrently on a build node:

stage('Build') {
    lock(resource: "compiler_${env.NODE_NAME}", inversePrecedence: true) {
      milestone 1
      sh "fastlane build_release"
    }
}

So if you wanted to prevent more than one job of the same branch running concurrently per node you could do something like

stage('Build') {
    lock(resource: "lock_${env.NODE_NAME}_${env.BRANCH_NAME}", inversePrecedence: true) {
      milestone 1
      sh "fastlane build_release"
    }
}

From: https://www.quernus.co.uk/2016/10/19/lockable-resources-jenkins-pipeline-builds/

Matt Hamilton
  • 805
  • 1
  • 9
  • 12
  • Using the code, I got the following error: >>Unknown stage section "lock". Starting with version 0.5, steps in a stage must be in a ‘steps’ block.<< I moved the "lock" section into a "steps" section, then it worked. – Erik Nellessen Sep 02 '19 at 07:22
  • 1
    What is the meaning of `inversePrecedence: true` while creating a `lock` ? – Yash May 02 '20 at 17:44
  • @Yash from [here](https://www.jenkins.io/doc/pipeline/steps/lockable-resources/) > By default waiting builds get the lock in the same order they requested to acquire it. By checking this option the newest build in the waiting queue will get the lock first. – Wolfson Nov 15 '21 at 17:08
  • While this is not what the OP asked for, this solution can be used to **prevent parallel execution of the subset of your build steps** and in this aspect it exceeds the solution of locking the full build with `disableConcurrentBuilds()` – sanya Jun 27 '23 at 08:28
39

Example using options block in the declarative pipeline syntax:

pipeline {

  options { 
    disableConcurrentBuilds() 
  }

...
}
Bryji
  • 1,206
  • 1
  • 13
  • 19
17

I think there are more than just one approach to this problem.

Pipeline

  • Use latest version of Lockable Resources Plugin and its lock step, as suggested in other answer.
  • If building the same project:
    • Uncheck Execute concurrent builds if necessary.
  • If building different projects:
    • Set different node or label for each project.

Jenkins

  • Limit number of node's executors to 1?

Plug-ins

luka5z
  • 7,525
  • 6
  • 29
  • 52
  • @sorin Can you be a little more specific about what you want to accomplish here? – luka5z Apr 07 '16 at 21:33
  • 2
    Limiting number of node executors is impossible as they are needed for other jobs. I just want to prevent same repository from being build in parallel. – sorin Apr 07 '16 at 22:16
  • 1
    Any idea on how to uncheck `Execute concurrent builds if necessary` from a `Jenkinsfile`? – Somatik Nov 02 '16 at 14:41
  • 2
    The "Throttle Concurrent Builds Plugin" now does have Pipeline support. Please see my answer below. – Mig82 Jun 13 '17 at 12:23
  • +1 to _Limit number of node's executors to 1_ - that is one of the best optimize a Jenkins installation. – mkobit Mar 21 '18 at 14:43
  • @luka5z - how do you all these steps in JenkinsFile as I'm using multibranch project and the UI doesn't have all these options? – Psdet Dec 09 '19 at 14:16
17

The "Throttle Concurrent Builds Plugin" now supports pipeline since throttle-concurrents-2.0. So now you can do something like this:

Fire the pipeline below twice, one immediately after the other and you will see. You can do this manually by double-clicking "Build Now" or by invoking it from a parallel step in another job.

stage('pre'){
    echo "I can run in parallel"
    sleep(time: 10, unit:'SECONDS')
}
throttle(['my-throttle-category']) {
    
    // Because only the node block is really throttled.
    echo "I can also run in parallel" 
    
    node('some-node-label') {
        
        echo "I can only run alone"
        
        stage('work') {
            
            echo "I also can only run alone"
            sleep(time: 10, unit:'SECONDS')
            
        }
    }
}
stage('post') {
    echo "I can run in parallel again"
    // Let's wait enough for the next execution to catch
    // up, just to illustrate.
    sleep(time: 20, unit:'SECONDS')
}

From the pipeline stage view you'll be able to appreciate this:

enter image description here

However, please be advised that this only works for node blocks within the throttle block. I do have other pipelines where I first allocate a node, then do some work which doesn't need throttling and then some which does.

node('some-node-label') {

    //do some concurrent work

    //This WILL NOT work.
    throttle(['my-throttle-category']) {
        //do some non-concurrent work
    }
}

In this case the throttle step doesn't solve the problem because the throttle step is the one inside the node step and not the other way around. In this case the lock step is better suited for the task

Mig82
  • 4,856
  • 4
  • 40
  • 63
8

Install Jenkins Lockable Resources Plugin.

In your pipeline script wrap the part in the lock block and give this lockable resource a name.

lock("test-server"){
    // your steps here
}

Use the name of whatever resource you are locking. In my experience its usually a test server or test database.

raitisd
  • 3,875
  • 5
  • 26
  • 37
  • 3
    Just to add - I found the lock feature but didn't think it solved my issue. However you can use variable names to define the lock resource - using env.NODE_NAME solved my issue where I needed to lock a stage to not run concurrently on the same node (it could run concurrently on different nodes) – Ed Mackenzie Oct 29 '18 at 16:52
  • If there's an exception or timeout inside the lock, it seems that the lock is never released and the other concurrent build keeps running... Until it timesout... :( Not sure but that's what I'm observing – Marcello DeSales Mar 29 '19 at 02:46
6

If you're like my team then you like having user-friendly parameterized Jenkins Jobs that pipeline scripts trigger in stages, instead of maintaining all that declarative/groovy soup. Unfortunately that means that each pipeline build takes up 2+ executor slots (one for the pipeline script and others for the triggered job(s)) so the danger of deadlock becomes very real.

I've looked everywhere for solutions to that dilemma, and disableConcurrentBuilds() only applies to the one pipeline where it is used - it prevents the pipeline from running two jobs at the same time. It won't have any impact on other pipelines.

A hacky (yet surprisingly elegant) solution for us was to limit the master node's executors to 1 and make the pipeline scripts stick to using it (and only it), then hook up a local slave agent to Jenkins in order to take care of all other jobs.

mirekphd
  • 4,799
  • 3
  • 38
  • 59
JohannSig
  • 131
  • 2
  • 8
  • Sorry, but this leads to mismanaging your employer's resources... How hard is it to imagine nodes with a triple number of CPU cores? – mirekphd Jul 02 '23 at 06:41
3

One of the options is to use Jenkins REST API. I researched for another options, but seems that this is only one available with pipelines functionality.

You should write script which polls Jenkins for info of current jobs running and check whether job of same type is running. To do this you should use Jenkins REST API, documentation you may find in the right bottom corner in your Jenkins page. Example script:

#!/usr/bin/env bash

# this script waits for integration test build finish
# usage: ./wait-for-tests.sh <jenkins_user_id> <jenkins_user_token_id>
jenkins_user=$1
jenkins_token=$2
build_number=$3

job_name="integration-tests"
branch="develop"

previous_build_number=build_number
let previous_build_number-=1
previous_job_status=$(curl -s http://${jenkins_user}:${jenkins_token}@jenkins.mycompany.com/job/mycompany/job/${job_name}/branch/${branch}/${previous_build_number}/api/json | jq -r '.result')

while [ "$previous_job_status" == "null" ];
do
    previous_job_status=$(curl -s http://${jenkins_user}:${jenkins_token}@jenkins.mycompany.com/job/mycompany/job/${job_name}/branch/${branch}/${previous_build_number}/api/json | jq -r '.result')
    echo "Waiting for tests completion"
    sleep 10
done

echo "Seems that tests are finished."

I've used bash here, but you may use any language. Then just call this script inside of your Jenkinsfile:

sh "./wait-for-tests.sh ${env.REMOTE_USER} ${env.REMOTE_TOKEN} ${env.BUILD_NUMBER}"

So it will wait until job completion (don't be confused with integration-test mentions, it's just job name).

Be also aware that in rare cases this script may cause deadlock when both jobs are waiting for each other, so you may want to implement some max retry policies here instead of infinite waiting.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Vova Rozhkov
  • 1,582
  • 2
  • 19
  • 27
  • 1
    Polling is A. Inefficient. and B. subject to race conditions. Moreover the "previous build" may in fact be a prior-prior build. How? Imagine I start 3 simultaneous builds with different parameters all at once. By the time the third build gets around to checking build #2, build #2 may have aborted (due to a parameter or job construction). Now build 3 thinks its in the clear because it doesn't see build #1. It's also possible that someone DELETED the prior build before build #3 got around to checking. Or it may have been auto-disposed of by log retention rules. Meanwhile 1 is still running. – Steven the Easily Amused Oct 02 '22 at 00:06
  • @SteventheEasilyAmused what's your suggestions? Btw, while I don't use Jenkins for a long time, this script flawelessy worked and solved my problem. – Vova Rozhkov Oct 03 '22 at 13:07
  • 1
    The question was: "How do I prevent two pipeline jenkins jobs of the same type to run in parallel on the same node?" My suggestion is to not use the solution in your answer. It doesn't prevent simultaneous builds, contains a race condition, requires a token and can block indefinitely. The solutions in other answers are reliable. (e.g. Restrict all nodes to single executors; Use Throttling; Use Locks; disable concurrent builds) – Steven the Easily Amused Oct 04 '22 at 17:44
1

Until the "Throttle Concurrent Builds" plugin has Pipeline support, a solution would be to effectively run one executor of the master with a label that your job requires.

To do this, create a new node in Jenkins, for example an SSH node that connects to localhost. You could also use the command option to run slave.jar/swarm.jar depending on your setup. Give the node one executor and a label like "resource-foo", and give your job this label as well. Now only one job of label "resource-foo" can run at a time because there is only one executor with that lable. If you set the node to be in use as much as possible (default) and reduce the number of master executors by one, it should behave exactly as desired without a change to total executors.

mrooney
  • 1,994
  • 2
  • 19
  • 30
1

Go to the node configuration and set the property Number of executors to 1.

  • This answer best addresses the question that was asked, but it does duplicate one of the [3 answers](https://stackoverflow.com/a/36487614/1655780) @luka5z provided years ago. – Steven the Easily Amused Oct 01 '22 at 23:59
0

If you are using "Generic Webhook Trigger Plugin" you need to update plugin to the latest version (above 1.86.0) and enable "Allow several triggers per build" within Job configurations.

Amir Babic
  • 41
  • 1