1 # -*- test-case-name: buildbot.test.test_locks -*-
3 from twisted
.python
import log
4 from twisted
.internet
import reactor
, defer
5 from buildbot
import util
7 if False: # for debugging
10 debuglog
= lambda m
: None
13 description
= "<BaseLock>"
15 def __init__(self
, name
, maxCount
=1):
19 self
.maxCount
=maxCount
22 return self
.description
24 def isAvailable(self
):
25 debuglog("%s isAvailable: self.owners=%r" % (self
, self
.owners
))
26 return len(self
.owners
) < self
.maxCount
28 def claim(self
, owner
):
29 debuglog("%s claim(%s)" % (self
, owner
))
30 assert owner
is not None
31 assert len(self
.owners
) < self
.maxCount
, "ask for isAvailable() first"
32 self
.owners
.append(owner
)
33 debuglog(" %s is claimed" % (self
,))
35 def release(self
, owner
):
36 debuglog("%s release(%s)" % (self
, owner
))
37 assert owner
in self
.owners
38 self
.owners
.remove(owner
)
41 d
= self
.waiting
.pop(0)
42 reactor
.callLater(0, d
.callback
, self
)
44 def waitUntilMaybeAvailable(self
, owner
):
45 """Fire when the lock *might* be available. The caller will need to
46 check with isAvailable() when the deferred fires. This loose form is
47 used to avoid deadlocks. If we were interested in a stronger form,
48 this would be named 'waitUntilAvailable', and the deferred would fire
49 after the lock had been claimed.
51 debuglog("%s waitUntilAvailable(%s)" % (self
, owner
))
52 if self
.isAvailable():
53 return defer
.succeed(self
)
55 self
.waiting
.append(d
)
59 class RealMasterLock(BaseLock
):
60 def __init__(self
, lockid
):
61 BaseLock
.__init
__(self
, lockid
.name
, lockid
.maxCount
)
62 self
.description
= "<MasterLock(%s, %s)>" % (self
.name
, self
.maxCount
)
64 def getLock(self
, slave
):
68 def __init__(self
, lockid
):
69 self
.name
= lockid
.name
70 self
.maxCount
= lockid
.maxCount
71 self
.maxCountForSlave
= lockid
.maxCountForSlave
72 self
.description
= "<SlaveLock(%s, %s, %s)>" % (self
.name
,
74 self
.maxCountForSlave
)
78 return self
.description
80 def getLock(self
, slavebuilder
):
81 slavename
= slavebuilder
.slave
.slavename
82 if not self
.locks
.has_key(slavename
):
83 maxCount
= self
.maxCountForSlave
.get(slavename
,
85 lock
= self
.locks
[slavename
] = BaseLock(self
.name
, maxCount
)
86 desc
= "<SlaveLock(%s, %s)[%s] %d>" % (self
.name
, maxCount
,
88 lock
.description
= desc
89 self
.locks
[slavename
] = lock
90 return self
.locks
[slavename
]
93 # master.cfg should only reference the following MasterLock and SlaveLock
94 # classes. They are identifiers that will be turned into real Locks later,
95 # via the BotMaster.getLockByID method.
97 class MasterLock(util
.ComparableMixin
):
98 """I am a semaphore that limits the number of simultaneous actions.
100 Builds and BuildSteps can declare that they wish to claim me as they run.
101 Only a limited number of such builds or steps will be able to run
102 simultaneously. By default this number is one, but my maxCount parameter
103 can be raised to allow two or three or more operations to happen at the
106 Use this to protect a resource that is shared among all builders and all
107 slaves, for example to limit the load on a common SVN repository.
110 compare_attrs
= ['name', 'maxCount']
111 lockClass
= RealMasterLock
112 def __init__(self
, name
, maxCount
=1):
114 self
.maxCount
= maxCount
116 class SlaveLock(util
.ComparableMixin
):
117 """I am a semaphore that limits simultaneous actions on each buildslave.
119 Builds and BuildSteps can declare that they wish to claim me as they run.
120 Only a limited number of such builds or steps will be able to run
121 simultaneously on any given buildslave. By default this number is one,
122 but my maxCount parameter can be raised to allow two or three or more
123 operations to happen on a single buildslave at the same time.
125 Use this to protect a resource that is shared among all the builds taking
126 place on each slave, for example to limit CPU or memory load on an
127 underpowered machine.
129 Each buildslave will get an independent copy of this semaphore. By
130 default each copy will use the same owner count (set with maxCount), but
131 you can provide maxCountForSlave with a dictionary that maps slavename to
132 owner count, to allow some slaves more parallelism than others.
136 compare_attrs
= ['name', 'maxCount', '_maxCountForSlaveList']
137 lockClass
= RealSlaveLock
138 def __init__(self
, name
, maxCount
=1, maxCountForSlave
={}):
140 self
.maxCount
= maxCount
141 self
.maxCountForSlave
= maxCountForSlave
142 # for comparison purposes, turn this dictionary into a stably-sorted
144 self
._maxCountForSlaveList
= self
.maxCountForSlave
.items()
145 self
._maxCountForSlaveList
.sort()
146 self
._maxCountForSlaveList
= tuple(self
._maxCountForSlaveList
)