1   
  2   
  3  from twisted.python import log 
  4  from twisted.internet import reactor, defer 
  5  from buildbot import util 
  6   
  7  if False:  
  8      debuglog = log.msg 
  9  else: 
 10      debuglog = lambda m: None 
 11   
 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   
 26          self.name = name         
 27          self.waiting = []        
 28          self.owners = []         
 29          self.maxCount=maxCount   
  30   
 33   
 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:  
 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   
 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               
 58              return num_excl == 0 and num_counting < self.maxCount 
 59          else: 
 60               
 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   
 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           
 83           
 84           
 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                   
 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   
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   
126   
129          self.name = lockid.name 
130          self.maxCount = lockid.maxCount 
131          self.maxCountForSlave = lockid.maxCountForSlave 
132          self.description = "<SlaveLock(%s, %s, %s)>" % (self.name, 
133                                                          self.maxCount, 
134                                                          self.maxCountForSlave) 
135          self.locks = {} 
 136   
139   
 151   
152   
154      """ I am an object representing a way to access a lock. 
155   
156      @param lockid: LockId instance that should be accessed. 
157      @type  lockid: A MasterLock or SlaveLock instance. 
158   
159      @param mode: Mode of accessing the lock. 
160      @type  mode: A string, either 'counting' or 'exclusive'. 
161      """ 
162   
163      compare_attrs = ['lockid', 'mode'] 
165          self.lockid = lockid 
166          self.mode = mode 
167   
168          assert isinstance(lockid, (MasterLock, SlaveLock)) 
169          assert mode in ['counting', 'exclusive'] 
  170   
171   
173      """ Abstract base class for LockId classes. 
174   
175      Sets up the 'access()' function for the LockId's available to the user 
176      (MasterLock and SlaveLock classes). 
177      Derived classes should add 
178      - Comparison with the L{util.ComparableMixin} via the L{compare_attrs} 
179        class variable. 
180      - Link to the actual lock class should be added with the L{lockClass} 
181        class variable. 
182      """ 
184          """ Express how the lock should be accessed """ 
185          assert mode in ['counting', 'exclusive'] 
186          return LockAccess(self, mode) 
 187   
189          """ For buildbot 0.7.7 compability: When user doesn't specify an access 
190              mode, this one is chosen. 
191          """ 
192          return self.access('counting') 
  193   
194   
195   
196   
197   
198   
199   
201      """I am a semaphore that limits the number of simultaneous actions. 
202   
203      Builds and BuildSteps can declare that they wish to claim me as they run. 
204      Only a limited number of such builds or steps will be able to run 
205      simultaneously. By default this number is one, but my maxCount parameter 
206      can be raised to allow two or three or more operations to happen at the 
207      same time. 
208   
209      Use this to protect a resource that is shared among all builders and all 
210      slaves, for example to limit the load on a common SVN repository. 
211      """ 
212   
213      compare_attrs = ['name', 'maxCount'] 
214      lockClass = RealMasterLock 
216          self.name = name 
217          self.maxCount = maxCount 
  218   
220      """I am a semaphore that limits simultaneous actions on each buildslave. 
221   
222      Builds and BuildSteps can declare that they wish to claim me as they run. 
223      Only a limited number of such builds or steps will be able to run 
224      simultaneously on any given buildslave. By default this number is one, 
225      but my maxCount parameter can be raised to allow two or three or more 
226      operations to happen on a single buildslave at the same time. 
227   
228      Use this to protect a resource that is shared among all the builds taking 
229      place on each slave, for example to limit CPU or memory load on an 
230      underpowered machine. 
231   
232      Each buildslave will get an independent copy of this semaphore. By 
233      default each copy will use the same owner count (set with maxCount), but 
234      you can provide maxCountForSlave with a dictionary that maps slavename to 
235      owner count, to allow some slaves more parallelism than others. 
236   
237      """ 
238   
239      compare_attrs = ['name', 'maxCount', '_maxCountForSlaveList'] 
240      lockClass = RealSlaveLock 
241 -    def __init__(self, name, maxCount=1, maxCountForSlave={}): 
 242          self.name = name 
243          self.maxCount = maxCount 
244          self.maxCountForSlave = maxCountForSlave 
245           
246           
247          self._maxCountForSlaveList = self.maxCountForSlave.items() 
248          self._maxCountForSlaveList.sort() 
249          self._maxCountForSlaveList = tuple(self._maxCountForSlaveList) 
  250