Package buildbot :: Module locks
[frames] | no frames]

Source Code for Module buildbot.locks

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  from twisted.python import log 
 18  from twisted.internet import reactor, defer 
 19  from buildbot import util 
 20   
 21  if False: # for debugging 
 22      debuglog = log.msg 
 23  else: 
 24      debuglog = lambda m: None 
 25   
26 -class BaseLock:
27 """ 28 Class handling claiming and releasing of L{self}, and keeping track of 29 current and waiting owners. 30 31 @note: Ideally, we'd like to maintain FIFO order. The place to do that 32 would be the L{isAvailable()} function. However, this function is 33 called by builds/steps both for the first time, and after waking 34 them up by L{self} from the L{self.waiting} queue. There is 35 currently no way of distinguishing between them. 36 """ 37 description = "<BaseLock>" 38
39 - def __init__(self, name, maxCount=1):
40 self.name = name # Name of the lock 41 self.waiting = [] # Current queue, tuples (LockAccess, deferred) 42 self.owners = [] # Current owners, tuples (owner, LockAccess) 43 self.maxCount = maxCount # maximal number of counting owners
44
45 - def __repr__(self):
46 return self.description
47
48 - def _getOwnersCount(self):
49 """ Return the number of current exclusive and counting owners. 50 51 @return: Tuple (number exclusive owners, number counting owners) 52 """ 53 num_excl, num_counting = 0, 0 54 for owner in self.owners: 55 if owner[1].mode == 'exclusive': 56 num_excl = num_excl + 1 57 else: # mode == 'counting' 58 num_counting = num_counting + 1 59 60 assert (num_excl == 1 and num_counting == 0) \ 61 or (num_excl == 0 and num_counting <= self.maxCount) 62 return num_excl, num_counting
63 64
65 - def isAvailable(self, access):
66 """ Return a boolean whether the lock is available for claiming """ 67 debuglog("%s isAvailable(%s): self.owners=%r" 68 % (self, access, self.owners)) 69 num_excl, num_counting = self._getOwnersCount() 70 if access.mode == 'counting': 71 # Wants counting access 72 return num_excl == 0 and num_counting < self.maxCount 73 else: 74 # Wants exclusive access 75 return num_excl == 0 and num_counting == 0
76
77 - def claim(self, owner, access):
78 """ Claim the lock (lock must be available) """ 79 debuglog("%s claim(%s, %s)" % (self, owner, access.mode)) 80 assert owner is not None 81 assert self.isAvailable(access), "ask for isAvailable() first" 82 83 assert isinstance(access, LockAccess) 84 assert access.mode in ['counting', 'exclusive'] 85 self.owners.append((owner, access)) 86 debuglog(" %s is claimed '%s'" % (self, access.mode))
87
88 - def release(self, owner, access):
89 """ Release the lock """ 90 assert isinstance(access, LockAccess) 91 92 debuglog("%s release(%s, %s)" % (self, owner, access.mode)) 93 entry = (owner, access) 94 assert entry in self.owners 95 self.owners.remove(entry) 96 # who can we wake up? 97 # After an exclusive access, we may need to wake up several waiting. 98 # Break out of the loop when the first waiting client should not be awakened. 99 num_excl, num_counting = self._getOwnersCount() 100 while len(self.waiting) > 0: 101 access, d = self.waiting[0] 102 if access.mode == 'counting': 103 if num_excl > 0 or num_counting == self.maxCount: 104 break 105 else: 106 num_counting = num_counting + 1 107 else: 108 # access.mode == 'exclusive' 109 if num_excl > 0 or num_counting > 0: 110 break 111 else: 112 num_excl = num_excl + 1 113 114 del self.waiting[0] 115 reactor.callLater(0, d.callback, self)
116
117 - def waitUntilMaybeAvailable(self, owner, access):
118 """Fire when the lock *might* be available. The caller will need to 119 check with isAvailable() when the deferred fires. This loose form is 120 used to avoid deadlocks. If we were interested in a stronger form, 121 this would be named 'waitUntilAvailable', and the deferred would fire 122 after the lock had been claimed. 123 """ 124 debuglog("%s waitUntilAvailable(%s)" % (self, owner)) 125 assert isinstance(access, LockAccess) 126 if self.isAvailable(access): 127 return defer.succeed(self) 128 d = defer.Deferred() 129 self.waiting.append((access, d)) 130 return d
131
132 - def stopWaitingUntilAvailable(self, owner, access, d):
133 debuglog("%s stopWaitingUntilAvailable(%s)" % (self, owner)) 134 assert isinstance(access, LockAccess) 135 assert (access, d) in self.waiting 136 self.waiting.remove( (access, d) )
137
138 - def isOwner(self, owner, access):
139 return (owner, access) in self.owners
140 141
142 -class RealMasterLock(BaseLock):
143 - def __init__(self, lockid):
144 BaseLock.__init__(self, lockid.name, lockid.maxCount) 145 self.description = "<MasterLock(%s, %s)>" % (self.name, self.maxCount)
146
147 - def getLock(self, slave):
148 return self
149
150 -class RealSlaveLock:
151 - def __init__(self, lockid):
152 self.name = lockid.name 153 self.maxCount = lockid.maxCount 154 self.maxCountForSlave = lockid.maxCountForSlave 155 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name, 156 self.maxCount, 157 self.maxCountForSlave) 158 self.locks = {}
159
160 - def __repr__(self):
161 return self.description
162
163 - def getLock(self, slavebuilder):
164 slavename = slavebuilder.slave.slavename 165 if not self.locks.has_key(slavename): 166 maxCount = self.maxCountForSlave.get(slavename, 167 self.maxCount) 168 lock = self.locks[slavename] = BaseLock(self.name, maxCount) 169 desc = "<SlaveLock(%s, %s)[%s] %d>" % (self.name, maxCount, 170 slavename, id(lock)) 171 lock.description = desc 172 self.locks[slavename] = lock 173 return self.locks[slavename]
174 175
176 -class LockAccess(util.ComparableMixin):
177 """ I am an object representing a way to access a lock. 178 179 @param lockid: LockId instance that should be accessed. 180 @type lockid: A MasterLock or SlaveLock instance. 181 182 @param mode: Mode of accessing the lock. 183 @type mode: A string, either 'counting' or 'exclusive'. 184 """ 185 186 compare_attrs = ['lockid', 'mode']
187 - def __init__(self, lockid, mode):
188 self.lockid = lockid 189 self.mode = mode 190 191 assert isinstance(lockid, (MasterLock, SlaveLock)) 192 assert mode in ['counting', 'exclusive']
193 194
195 -class BaseLockId(util.ComparableMixin):
196 """ Abstract base class for LockId classes. 197 198 Sets up the 'access()' function for the LockId's available to the user 199 (MasterLock and SlaveLock classes). 200 Derived classes should add 201 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs} 202 class variable. 203 - Link to the actual lock class should be added with the L{lockClass} 204 class variable. 205 """
206 - def access(self, mode):
207 """ Express how the lock should be accessed """ 208 assert mode in ['counting', 'exclusive'] 209 return LockAccess(self, mode)
210
211 - def defaultAccess(self):
212 """ For buildbot 0.7.7 compability: When user doesn't specify an access 213 mode, this one is chosen. 214 """ 215 return self.access('counting')
216 217 218 219 # master.cfg should only reference the following MasterLock and SlaveLock 220 # classes. They are identifiers that will be turned into real Locks later, 221 # via the BotMaster.getLockByID method. 222
223 -class MasterLock(BaseLockId):
224 """I am a semaphore that limits the number of simultaneous actions. 225 226 Builds and BuildSteps can declare that they wish to claim me as they run. 227 Only a limited number of such builds or steps will be able to run 228 simultaneously. By default this number is one, but my maxCount parameter 229 can be raised to allow two or three or more operations to happen at the 230 same time. 231 232 Use this to protect a resource that is shared among all builders and all 233 slaves, for example to limit the load on a common SVN repository. 234 """ 235 236 compare_attrs = ['name', 'maxCount'] 237 lockClass = RealMasterLock
238 - def __init__(self, name, maxCount=1):
239 self.name = name 240 self.maxCount = maxCount
241
242 -class SlaveLock(BaseLockId):
243 """I am a semaphore that limits simultaneous actions on each buildslave. 244 245 Builds and BuildSteps can declare that they wish to claim me as they run. 246 Only a limited number of such builds or steps will be able to run 247 simultaneously on any given buildslave. By default this number is one, 248 but my maxCount parameter can be raised to allow two or three or more 249 operations to happen on a single buildslave at the same time. 250 251 Use this to protect a resource that is shared among all the builds taking 252 place on each slave, for example to limit CPU or memory load on an 253 underpowered machine. 254 255 Each buildslave will get an independent copy of this semaphore. By 256 default each copy will use the same owner count (set with maxCount), but 257 you can provide maxCountForSlave with a dictionary that maps slavename to 258 owner count, to allow some slaves more parallelism than others. 259 260 """ 261 262 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList'] 263 lockClass = RealSlaveLock
264 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
265 self.name = name 266 self.maxCount = maxCount 267 self.maxCountForSlave = maxCountForSlave 268 # for comparison purposes, turn this dictionary into a stably-sorted 269 # list of tuples 270 self._maxCountForSlaveList = self.maxCountForSlave.items() 271 self._maxCountForSlaveList.sort() 272 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
273