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

Source Code for Module buildbot.locks

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