1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  from twisted.python import log 
 18  from twisted.internet import reactor, defer 
 19  from buildbot import util 
 20   
 21  if False:  
 22      debuglog = log.msg 
 23  else: 
 24      debuglog = lambda m: None 
 25   
 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   
 40          self.name = name           
 41          self.waiting = []          
 42          self.owners = []           
 43          self.maxCount = maxCount   
  44   
 47   
 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:  
 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   
 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               
 72              return num_excl == 0 and num_counting < self.maxCount 
 73          else: 
 74               
 75              return num_excl == 0 and num_counting == 0 
  76   
 77 -    def claim(self, owner, access): 
  87   
 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           
 97           
 98           
 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                   
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   
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   
137   
139          return (owner, access) in self.owners 
  140   
141   
149   
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   
162   
 174   
175   
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'] 
 193   
194   
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      """ 
207          """ Express how the lock should be accessed """ 
208          assert mode in ['counting', 'exclusive'] 
209          return LockAccess(self, mode) 
 210   
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   
220   
221   
222   
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 
239          self.name = name 
240          self.maxCount = maxCount 
  241   
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           
269           
270          self._maxCountForSlaveList = self.maxCountForSlave.items() 
271          self._maxCountForSlaveList.sort() 
272          self._maxCountForSlaveList = tuple(self._maxCountForSlaveList) 
  273