| 1 |
Until now, we assumed that a master can run builds at any slave whenever needed |
|---|
| 2 |
or desired. |
|---|
| 3 |
Some times, you want to enforce additional constraints on builds. For reasons |
|---|
| 4 |
like limited network bandwidth, old slave machines, or a self-willed data base |
|---|
| 5 |
server, you may want to limit the number of builds (or build steps) that can |
|---|
| 6 |
access a resource. |
|---|
| 7 |
|
|---|
| 8 |
The mechanism used by Buildbot is known as the read/write lock.@footnote{See |
|---|
| 9 |
http://en.wikipedia.org/wiki/Read/write_lock_pattern for more information.} |
|---|
| 10 |
It allows either many readers or a single writer but not a combination of |
|---|
| 11 |
readers and writers. |
|---|
| 12 |
The general lock has been modified and extended for use in Buildbot. |
|---|
| 13 |
Firstly, the general lock allows an infinite number of readers. In Buildbot, |
|---|
| 14 |
we often want to put an upper limit on the number of readers, for example |
|---|
| 15 |
allowing two out of five possible builds at the same time. To do this, the lock |
|---|
| 16 |
counts the number of active readers. |
|---|
| 17 |
Secondly, the terms @emph{read mode} and @emph{write mode} are confusing in |
|---|
| 18 |
Buildbot context. They have been replaced by @emph{counting mode} (since the |
|---|
| 19 |
lock counts them) and @{exclusive mode}. |
|---|
| 20 |
As a result of these changes, locks in Buildbot allow a number of builds (upto |
|---|
| 21 |
some fixed number) in counting mode, or they allow one build in exclusive mode. |
|---|
| 22 |
|
|---|
| 23 |
Often, not all slaves are equal. To allow for this situation, Buildbot allows to |
|---|
| 24 |
have a seperate upper limit on the count for each slave. In this way, you can |
|---|
| 25 |
have at most 3 concurrent builds at a fast slave, 2 at a slightly older slave, |
|---|
| 26 |
and 1 at all other slaves. |
|---|
| 27 |
|
|---|
| 28 |
The final thing you can specify when you introduce a new lock is its scope. Some |
|---|
| 29 |
constraints are global, they must be enforced over all slaves. Other constraints |
|---|
| 30 |
are local to each slave. |
|---|
| 31 |
A @emph{master lock} is used for the global constraints. You can ensure for |
|---|
| 32 |
example that at most one build (of all builds running at all slaves) accesses |
|---|
| 33 |
the data base server. With a @emph{slave lock} you can add a limit local for |
|---|
| 34 |
each slave. With such a lock, you can for example enforce an upper limit to the |
|---|
| 35 |
number of active builds at a slave, like above. |
|---|
| 36 |
|
|---|
| 37 |
Time for a few examples. Below a master lock is defined to protect a data base, |
|---|
| 38 |
and a slave lock is created to limit the number of builds at each slave. |
|---|
| 39 |
@verbatim |
|---|
| 40 |
from buildbot import locks |
|---|
| 41 |
|
|---|
| 42 |
db_lock = locks.MasterLock("database") |
|---|
| 43 |
build_lock = locks.SlaveLock("slave_builds", |
|---|
| 44 |
maxCount = 1, |
|---|
| 45 |
maxCountForSlave = { 'fast': 3, 'new': 2 }) |
|---|
| 46 |
@end verbatim |
|---|
| 47 |
After importing locks from buildbot, @code{db_lock} is defined to be a master |
|---|
| 48 |
lock. The @code{"database"} string is used for uniquely identifying the lock. |
|---|
| 49 |
At the next line, a slave lock called @code{build_lock} is created. It is |
|---|
| 50 |
identified by the @code{"slave_builds"} string. Since the requirements of the |
|---|
| 51 |
lock are a bit more complicated, two optional arguments are also specified. The |
|---|
| 52 |
@code{maxCount} parameter sets the default limit for builds in counting mode to |
|---|
| 53 |
@code{1}. For the slave called @code{'fast'} however, we want to have at most |
|---|
| 54 |
three builds, and for the slave called @code{'new'} the upper limit is two |
|---|
| 55 |
builds running at the same time. |
|---|
| 56 |
|
|---|
| 57 |
The next step is using the locks in builds. |
|---|
| 58 |
Buildbot allows a lock to be used during an entire build (from beginning to |
|---|
| 59 |
end), or only during a single build step. In the latter case, the lock is |
|---|
| 60 |
claimed for use just before the step starts, and released again when the step |
|---|
| 61 |
ends. To prevent deadlocks,@footnote{Deadlock is the situation where two or more |
|---|
| 62 |
slaves each hold a lock in exclusive mode, and in addition want to claim the |
|---|
| 63 |
lock held by the other slave exclusively as well. Since locks allow at most one |
|---|
| 64 |
exclusive user, both slaves will wait forever.} it is not possible to claim or |
|---|
| 65 |
release locks at other times. |
|---|
| 66 |
|
|---|
| 67 |
To use locks, you should add them with a @code{locks} argument. |
|---|
| 68 |
Each use of a lock is either in counting mode (that is, possibly shared with |
|---|
| 69 |
other builds) or in exclusive mode. A build or build step proceeds only when it |
|---|
| 70 |
has acquired all locks. If a build or step needs a lot of locks, it may be |
|---|
| 71 |
starved@footnote{Starving is the situation that only a few locks are available, |
|---|
| 72 |
and they are immediately grabbed by another build. As a result, it may take a |
|---|
| 73 |
long time before all locks needed by the starved build are free at the same |
|---|
| 74 |
time.} by other builds that need less locks. |
|---|
| 75 |
|
|---|
| 76 |
To illustrate use of locks, a few examples. |
|---|
| 77 |
@example |
|---|
| 78 |
from buildbot import locks |
|---|
| 79 |
from buildbot.steps import source, shell |
|---|
| 80 |
from buildbot.process import factory |
|---|
| 81 |
|
|---|
| 82 |
db_lock = locks.MasterLock("database") |
|---|
| 83 |
build_lock = locks.SlaveLock("slave_builds", |
|---|
| 84 |
maxCount = 1, |
|---|
| 85 |
maxCountForSlave = { 'fast': 3, 'new': 2 }) |
|---|
| 86 |
|
|---|
| 87 |
f = factory.BuildFactory() |
|---|
| 88 |
f.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) |
|---|
| 89 |
f.addStep(shell.ShellCommand(command="make all")) |
|---|
| 90 |
f.addStep(shell.ShellCommand(command="make test", |
|---|
| 91 |
locks=[db_lock.access('exclusive')])) |
|---|
| 92 |
|
|---|
| 93 |
b1 = @{'name': 'full1', 'slavename': 'fast', 'builddir': 'f1', 'factory': f, |
|---|
| 94 |
'locks': [build_lock.access('counting')] @} |
|---|
| 95 |
|
|---|
| 96 |
b2 = @{'name': 'full2', 'slavename': 'new', 'builddir': 'f2', 'factory': f. |
|---|
| 97 |
'locks': [build_lock.access('counting')] @} |
|---|
| 98 |
|
|---|
| 99 |
b3 = @{'name': 'full3', 'slavename': 'old', 'builddir': 'f3', 'factory': f. |
|---|
| 100 |
'locks': [build_lock.access('counting')] @} |
|---|
| 101 |
|
|---|
| 102 |
b4 = @{'name': 'full4', 'slavename': 'other', 'builddir': 'f4', 'factory': f. |
|---|
| 103 |
'locks': [build_lock.access('counting')] @} |
|---|
| 104 |
|
|---|
| 105 |
c['builders'] = [b1, b2, b3, b4] |
|---|
| 106 |
@end example |
|---|
| 107 |
Here we have four slaves @code{b1}, @code{b2}, @code{b3}, and @code{b4}. Each |
|---|
| 108 |
slave performs the same checkout, make, and test build step sequence. |
|---|
| 109 |
We want to enforce that at most one test step is executed between all slaves due |
|---|
| 110 |
to restrictions with the data base server. This is done by adding the |
|---|
| 111 |
@code{locks=} parameter with the third step. It takes a list of locks with their |
|---|
| 112 |
access mode. In this case only the @code{db_lock} is needed. The exclusive |
|---|
| 113 |
access mode is used to ensure there is at most one slave that executes the test |
|---|
| 114 |
step. |
|---|
| 115 |
|
|---|
| 116 |
In addition to exclusive accessing the data base, we also want that slaves stay |
|---|
| 117 |
responsive even under the load of a large number of builds being triggered. For |
|---|
| 118 |
this purpose, the slave lock called @code{build_lock} is defined. Since the |
|---|
| 119 |
restraint holds for entire builds, the lock is specified in the builder with |
|---|
| 120 |
@code{'locks': [build_lock.access('counting')]}. |
|---|