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