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  from buildbot.util import subscription 
 21   
 22  if False: # for debugging 
 23      debuglog = log.msg 
 24  else: 
 25      debuglog = lambda m: None 
 26   
27 -class BaseLock:
28 """ 29 Class handling claiming and releasing of L{self}, and keeping track of 30 current and waiting owners. 31 32 We maintain the wait queue in FIFO order, and ensure that counting waiters 33 in the queue behind exclusive waiters cannot acquire the lock. This ensures 34 that exclusive waiters are not starved. 35 """ 36 description = "<BaseLock>" 37
38 - def __init__(self, name, maxCount=1):
39 self.name = name # Name of the lock 40 self.waiting = [] # Current queue, tuples (waiter, LockAccess, 41 # deferred) 42 self.owners = [] # Current owners, tuples (owner, LockAccess) 43 self.maxCount = maxCount # maximal number of counting owners 44 45 # subscriptions to this lock being released 46 self.release_subs = subscription.SubscriptionPoint("%r releases" 47 % (self,))
48
49 - def __repr__(self):
50 return self.description
51
52 - def _getOwnersCount(self):
53 """ Return the number of current exclusive and counting owners. 54 55 @return: Tuple (number exclusive owners, number counting owners) 56 """ 57 num_excl, num_counting = 0, 0 58 for owner in self.owners: 59 if owner[1].mode == 'exclusive': 60 num_excl = num_excl + 1 61 else: # mode == 'counting' 62 num_counting = num_counting + 1 63 64 assert (num_excl == 1 and num_counting == 0) \ 65 or (num_excl == 0 and num_counting <= self.maxCount) 66 return num_excl, num_counting
67 68
69 - def isAvailable(self, requester, access):
70 """ Return a boolean whether the lock is available for claiming """ 71 debuglog("%s isAvailable(%s, %s): self.owners=%r" 72 % (self, requester, access, self.owners)) 73 num_excl, num_counting = self._getOwnersCount() 74 75 # Find all waiters ahead of the requester in the wait queue 76 for idx, waiter in enumerate(self.waiting): 77 if waiter[0] == requester: 78 w_index = idx 79 break 80 else: 81 w_index = len(self.waiting) 82 ahead = self.waiting[:w_index] 83 84 if access.mode == 'counting': 85 # Wants counting access 86 return num_excl == 0 and num_counting + len(ahead) < self.maxCount \ 87 and all([w[1].mode == 'counting' for w in ahead]) 88 else: 89 # Wants exclusive access 90 return num_excl == 0 and num_counting == 0 and len(ahead) == 0
91
92 - def claim(self, owner, access):
93 """ Claim the lock (lock must be available) """ 94 debuglog("%s claim(%s, %s)" % (self, owner, access.mode)) 95 assert owner is not None 96 assert self.isAvailable(owner, access), "ask for isAvailable() first" 97 98 assert isinstance(access, LockAccess) 99 assert access.mode in ['counting', 'exclusive'] 100 self.waiting = [w for w in self.waiting if w[0] != owner] 101 self.owners.append((owner, access)) 102 debuglog(" %s is claimed '%s'" % (self, access.mode))
103
104 - def subscribeToReleases(self, callback):
105 """Schedule C{callback} to be invoked every time this lock is 106 released. Returns a L{Subscription}.""" 107 return self.release_subs.subscribe(callback)
108
109 - def release(self, owner, access):
110 """ Release the lock """ 111 assert isinstance(access, LockAccess) 112 113 debuglog("%s release(%s, %s)" % (self, owner, access.mode)) 114 entry = (owner, access) 115 if not entry in self.owners: 116 debuglog("%s already released" % self) 117 return 118 self.owners.remove(entry) 119 # who can we wake up? 120 # After an exclusive access, we may need to wake up several waiting. 121 # Break out of the loop when the first waiting client should not be awakened. 122 num_excl, num_counting = self._getOwnersCount() 123 for i, (w_owner, w_access, d) in enumerate(self.waiting): 124 if w_access.mode == 'counting': 125 if num_excl > 0 or num_counting == self.maxCount: 126 break 127 else: 128 num_counting = num_counting + 1 129 else: 130 # w_access.mode == 'exclusive' 131 if num_excl > 0 or num_counting > 0: 132 break 133 else: 134 num_excl = num_excl + 1 135 136 # If the waiter has a deferred, wake it up and clear the deferred 137 # from the wait queue entry to indicate that it has been woken. 138 if d: 139 self.waiting[i] = (w_owner, w_access, None) 140 reactor.callLater(0, d.callback, self) 141 142 # notify any listeners 143 self.release_subs.deliver()
144
145 - def waitUntilMaybeAvailable(self, owner, access):
146 """Fire when the lock *might* be available. The caller will need to 147 check with isAvailable() when the deferred fires. This loose form is 148 used to avoid deadlocks. If we were interested in a stronger form, 149 this would be named 'waitUntilAvailable', and the deferred would fire 150 after the lock had been claimed. 151 """ 152 debuglog("%s waitUntilAvailable(%s)" % (self, owner)) 153 assert isinstance(access, LockAccess) 154 if self.isAvailable(owner, access): 155 return defer.succeed(self) 156 d = defer.Deferred() 157 158 # Are we already in the wait queue? 159 w = [i for i, w in enumerate(self.waiting) if w[0] == owner] 160 if w: 161 self.waiting[w[0]] = (owner, access, d) 162 else: 163 self.waiting.append((owner, access, d)) 164 return d
165
166 - def stopWaitingUntilAvailable(self, owner, access, d):
167 debuglog("%s stopWaitingUntilAvailable(%s)" % (self, owner)) 168 assert isinstance(access, LockAccess) 169 assert (owner, access, d) in self.waiting 170 self.waiting = [w for w in self.waiting if w[0] != owner]
171
172 - def isOwner(self, owner, access):
173 return (owner, access) in self.owners
174 175
176 -class RealMasterLock(BaseLock):
177 - def __init__(self, lockid):
178 BaseLock.__init__(self, lockid.name, lockid.maxCount) 179 self.description = "<MasterLock(%s, %s)>" % (self.name, self.maxCount)
180
181 - def getLock(self, slave):
182 return self
183
184 -class RealSlaveLock:
185 - def __init__(self, lockid):
186 self.name = lockid.name 187 self.maxCount = lockid.maxCount 188 self.maxCountForSlave = lockid.maxCountForSlave 189 self.description = "<SlaveLock(%s, %s, %s)>" % (self.name, 190 self.maxCount, 191 self.maxCountForSlave) 192 self.locks = {}
193
194 - def __repr__(self):
195 return self.description
196
197 - def getLock(self, slave):
198 slavename = slave.slavename 199 if not self.locks.has_key(slavename): 200 maxCount = self.maxCountForSlave.get(slavename, 201 self.maxCount) 202 lock = self.locks[slavename] = BaseLock(self.name, maxCount) 203 desc = "<SlaveLock(%s, %s)[%s] %d>" % (self.name, maxCount, 204 slavename, id(lock)) 205 lock.description = desc 206 self.locks[slavename] = lock 207 return self.locks[slavename]
208 209
210 -class LockAccess(util.ComparableMixin):
211 """ I am an object representing a way to access a lock. 212 213 @param lockid: LockId instance that should be accessed. 214 @type lockid: A MasterLock or SlaveLock instance. 215 216 @param mode: Mode of accessing the lock. 217 @type mode: A string, either 'counting' or 'exclusive'. 218 """ 219 220 compare_attrs = ['lockid', 'mode']
221 - def __init__(self, lockid, mode):
222 self.lockid = lockid 223 self.mode = mode 224 225 assert isinstance(lockid, (MasterLock, SlaveLock)) 226 assert mode in ['counting', 'exclusive']
227 228
229 -class BaseLockId(util.ComparableMixin):
230 """ Abstract base class for LockId classes. 231 232 Sets up the 'access()' function for the LockId's available to the user 233 (MasterLock and SlaveLock classes). 234 Derived classes should add 235 - Comparison with the L{util.ComparableMixin} via the L{compare_attrs} 236 class variable. 237 - Link to the actual lock class should be added with the L{lockClass} 238 class variable. 239 """
240 - def access(self, mode):
241 """ Express how the lock should be accessed """ 242 assert mode in ['counting', 'exclusive'] 243 return LockAccess(self, mode)
244
245 - def defaultAccess(self):
246 """ For buildbot 0.7.7 compability: When user doesn't specify an access 247 mode, this one is chosen. 248 """ 249 return self.access('counting')
250 251 252 253 # master.cfg should only reference the following MasterLock and SlaveLock 254 # classes. They are identifiers that will be turned into real Locks later, 255 # via the BotMaster.getLockByID method. 256
257 -class MasterLock(BaseLockId):
258 """I am a semaphore that limits the number of simultaneous actions. 259 260 Builds and BuildSteps can declare that they wish to claim me as they run. 261 Only a limited number of such builds or steps will be able to run 262 simultaneously. By default this number is one, but my maxCount parameter 263 can be raised to allow two or three or more operations to happen at the 264 same time. 265 266 Use this to protect a resource that is shared among all builders and all 267 slaves, for example to limit the load on a common SVN repository. 268 """ 269 270 compare_attrs = ['name', 'maxCount'] 271 lockClass = RealMasterLock
272 - def __init__(self, name, maxCount=1):
273 self.name = name 274 self.maxCount = maxCount
275
276 -class SlaveLock(BaseLockId):
277 """I am a semaphore that limits simultaneous actions on each buildslave. 278 279 Builds and BuildSteps can declare that they wish to claim me as they run. 280 Only a limited number of such builds or steps will be able to run 281 simultaneously on any given buildslave. By default this number is one, 282 but my maxCount parameter can be raised to allow two or three or more 283 operations to happen on a single buildslave at the same time. 284 285 Use this to protect a resource that is shared among all the builds taking 286 place on each slave, for example to limit CPU or memory load on an 287 underpowered machine. 288 289 Each buildslave will get an independent copy of this semaphore. By 290 default each copy will use the same owner count (set with maxCount), but 291 you can provide maxCountForSlave with a dictionary that maps slavename to 292 owner count, to allow some slaves more parallelism than others. 293 294 """ 295 296 compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList'] 297 lockClass = RealSlaveLock
298 - def __init__(self, name, maxCount=1, maxCountForSlave={}):
299 self.name = name 300 self.maxCount = maxCount 301 self.maxCountForSlave = maxCountForSlave 302 # for comparison purposes, turn this dictionary into a stably-sorted 303 # list of tuples 304 self._maxCountForSlaveList = self.maxCountForSlave.items() 305 self._maxCountForSlaveList.sort() 306 self._maxCountForSlaveList = tuple(self._maxCountForSlaveList)
307