Ticket #313: lockaccess2.patch

File lockaccess2.patch, 29.2 kB (added by albertHofkamp, 6 months ago)

the lock access extension

  • buildbot-0.7.7/buildbot/locks.py

    old new  
    2323    description = "<BaseLock>" 
    2424 
    2525    def __init__(self, name, maxCount=1): 
    26         self.name = name 
    27         self.waiting = [] 
    28         self.owners = [] 
    29         self.maxCount=maxCount 
     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 
    3030 
    3131    def __repr__(self): 
    3232        return self.description 
    3333 
    34     def isAvailable(self): 
     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): 
    3552        """ Return a boolean whether the lock is available for claiming """ 
    36         debuglog("%s isAvailable: self.owners=%r" % (self, self.owners)) 
    37         return len(self.owners) < self.maxCount 
     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 
    3862 
    39     def claim(self, owner): 
     63    def claim(self, owner, access): 
    4064        """ Claim the lock (lock must be available) """ 
    41         debuglog("%s claim(%s)" % (self, owner)) 
     65        debuglog("%s claim(%s, %s)" % (self, owner, access.mode)) 
    4266        assert owner is not None 
    43         assert len(self.owners) < self.maxCount, "ask for isAvailable() first" 
    44         self.owners.append(owner) 
    45         debuglog(" %s is claimed" % (self,)) 
    46  
    47     def release(self, owner): 
     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): 
    4875        """ Release the lock """ 
    49         debuglog("%s release(%s)" % (self, owner)) 
    50         assert owner in self.owners 
    51         self.owners.remove(owner) 
     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) 
    5282        # who can we wake up? 
    53         if self.waiting: 
    54             d = self.waiting.pop(0) 
     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] 
    55101            reactor.callLater(0, d.callback, self) 
    56102 
    57     def waitUntilMaybeAvailable(self, owner): 
     103    def waitUntilMaybeAvailable(self, owner, access): 
    58104        """Fire when the lock *might* be available. The caller will need to 
    59105        check with isAvailable() when the deferred fires. This loose form is 
    60106        used to avoid deadlocks. If we were interested in a stronger form, 
     
    62108        after the lock had been claimed. 
    63109        """ 
    64110        debuglog("%s waitUntilAvailable(%s)" % (self, owner)) 
    65         if self.isAvailable(): 
     111        assert isinstance(access, LockAccess) 
     112        if self.isAvailable(access): 
    66113            return defer.succeed(self) 
    67114        d = defer.Deferred() 
    68         self.waiting.append(d
     115        self.waiting.append((access, d)
    69116        return d 
    70117 
    71118 
     
    103150        return self.locks[slavename] 
    104151 
    105152 
     153class LockAccess: 
     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    def __init__(self, lockid, mode): 
     163        self.lockid = lockid 
     164        self.mode = mode 
     165 
     166        assert isinstance(lockid, (MasterLock, SlaveLock)) 
     167        assert mode in ['counting', 'exclusive'] 
     168 
     169 
    106170class BaseLockId(util.ComparableMixin): 
    107171    """ Abstract base class for LockId classes. 
    108172 
     173    Sets up the 'access()' function for the LockId's available to the user 
     174    (MasterLock and SlaveLock classes). 
    109175    Derived classes should add 
    110176    - Comparison with the L{util.ComparableMixin} via the L{compare_attrs} 
    111177      class variable. 
    112178    - Link to the actual lock class should be added with the L{lockClass} 
    113179      class variable. 
    114180    """ 
     181    def access(self, mode): 
     182        """ Express how the lock should be accessed """ 
     183        assert mode in ['counting', 'exclusive'] 
     184        return LockAccess(self, mode) 
     185 
     186    def defaultAccess(self): 
     187        """ For buildbot 0.7.7 compability: When user doesn't specify an access 
     188            mode, this one is chosen. 
     189        """ 
     190        return self.access('counting') 
     191 
     192 
    115193 
    116194# master.cfg should only reference the following MasterLock and SlaveLock 
    117195# classes. They are identifiers that will be turned into real Locks later, 
  • buildbot-0.7.7/buildbot/master.py

    old new  
    633633        lock_dict = {} 
    634634        for b in builders: 
    635635            for l in b.get('locks', []): 
     636                if isinstance(l, locks.LockAccess): # User specified access to the lock 
     637                    l = l.lockid 
    636638                if lock_dict.has_key(l.name): 
    637639                    if lock_dict[l.name] is not l: 
    638640                        raise ValueError("Two different locks (%s and %s) " 
     
    645647            # important. 
    646648            for s in b['factory'].steps: 
    647649                for l in s[1].get('locks', []): 
     650                    if isinstance(l, locks.LockAccess): # User specified access to the lock 
     651                        l = l.lockid 
    648652                    if lock_dict.has_key(l.name): 
    649653                        if lock_dict[l.name] is not l: 
    650654                            raise ValueError("Two different locks (%s and %s)" 
  • buildbot-0.7.7/buildbot/process/base.py

    old new  
    77from twisted.python.failure import Failure 
    88from twisted.internet import reactor, defer, error 
    99 
    10 from buildbot import interfaces 
     10from buildbot import interfaces, locks 
    1111from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION 
    1212from buildbot.status.builder import Results, BuildRequestStatus 
    1313from buildbot.status.progress import BuildProgress 
     
    284284        self.setupSlaveBuilder(slavebuilder) 
    285285 
    286286        # convert all locks into their real forms 
    287         self.locks = [self.builder.botmaster.getLockByID(l) 
    288                       for l in self.locks] 
     287        lock_list = [] 
     288        for access in self.locks: 
     289            if not isinstance(access, locks.LockAccess): 
     290                # Buildbot 0.7.7 compability: user did not specify access 
     291                access = access.defaultAccess() 
     292            lock = self.builder.botmaster.getLockByID(access.lockid) 
     293            lock_list.append((lock, access)) 
     294        self.locks = lock_list 
    289295        # then narrow SlaveLocks down to the right slave 
    290         self.locks = [l.getLock(self.slavebuilder) for l in self.locks] 
     296        self.locks = [(l.getLock(self.slavebuilder), la) 
     297                       for l, la in self.locks] 
    291298        self.remote = slavebuilder.remote 
    292299        self.remote.notifyOnDisconnect(self.lostRemote) 
    293300        d = self.deferred = defer.Deferred() 
     
    323330        log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks)) 
    324331        if not self.locks: 
    325332            return defer.succeed(None) 
    326         for lock in self.locks: 
    327             if not lock.isAvailable(): 
     333        for lock, access in self.locks: 
     334            if not lock.isAvailable(access): 
    328335                log.msg("Build %s waiting for lock %s" % (self, lock)) 
    329                 d = lock.waitUntilMaybeAvailable(self
     336                d = lock.waitUntilMaybeAvailable(self, access
    330337                d.addCallback(self.acquireLocks) 
    331338                return d 
    332339        # all locks are available, claim them all 
    333         for lock in self.locks: 
    334             lock.claim(self
     340        for lock, access in self.locks: 
     341            lock.claim(self, access
    335342        return defer.succeed(None) 
    336343 
    337344    def _startBuild_2(self, res): 
     
    555562 
    556563    def releaseLocks(self): 
    557564        log.msg("releaseLocks(%s): %s" % (self, self.locks)) 
    558         for lock in self.locks: 
    559             lock.release(self
     565        for lock, access in self.locks: 
     566            lock.release(self, access
    560567 
    561568    # IBuildControl 
    562569 
  • buildbot-0.7.7/buildbot/process/buildstep.py

    old new  
    88from twisted.python.failure import Failure 
    99from twisted.web.util import formatFailure 
    1010 
    11 from buildbot import util 
     11from buildbot import util, locks 
    1212from buildbot import interfaces 
    1313from buildbot.status import progress 
    1414from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \ 
     
    663663        self.remote = remote 
    664664        self.deferred = defer.Deferred() 
    665665        # convert all locks into their real form 
    666         self.locks = [self.build.builder.botmaster.getLockByID(l) 
    667                       for l in self.locks] 
     666        lock_list = [] 
     667        for access in self.locks: 
     668            if not isinstance(access, locks.LockAccess): 
     669                # Buildbot 0.7.7 compability: user did not specify access 
     670                access = access.defaultAccess() 
     671            lock = self.build.builder.botmaster.getLockByID(access.lockid) 
     672            lock_list.append((lock, access)) 
     673        self.locks = lock_list 
    668674        # then narrow SlaveLocks down to the slave that this build is being 
    669675        # run on 
    670         self.locks = [l.getLock(self.build.slavebuilder) for l in self.locks] 
    671         for l in self.locks: 
     676        self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks] 
     677        for l, la in self.locks: 
    672678            if l in self.build.locks: 
    673679                log.msg("Hey, lock %s is claimed by both a Step (%s) and the" 
    674680                        " parent Build (%s)" % (l, self, self.build)) 
     
    681687        log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks)) 
    682688        if not self.locks: 
    683689            return defer.succeed(None) 
    684         for lock in self.locks: 
    685             if not lock.isAvailable(): 
     690        for lock, access in self.locks: 
     691            if not lock.isAvailable(access): 
    686692                log.msg("step %s waiting for lock %s" % (self, lock)) 
    687                 d = lock.waitUntilMaybeAvailable(self
     693                d = lock.waitUntilMaybeAvailable(self, access
    688694                d.addCallback(self.acquireLocks) 
    689695                return d 
    690696        # all locks are available, claim them all 
    691         for lock in self.locks: 
    692             lock.claim(self
     697        for lock, access in self.locks: 
     698            lock.claim(self, access
    693699        return defer.succeed(None) 
    694700 
    695701    def _startStep_2(self, res): 
     
    770776 
    771777    def releaseLocks(self): 
    772778        log.msg("releaseLocks(%s): %s" % (self, self.locks)) 
    773         for lock in self.locks: 
    774             lock.release(self
     779        for lock, access in self.locks: 
     780            lock.release(self, access
    775781 
    776782    def finished(self, results): 
    777783        if self.progress: 
  • buildbot-0.7.7/buildbot/test/test_locks.py

    old new  
    1212from buildbot.test.runutils import RunMixin 
    1313from buildbot import locks 
    1414 
    15 def claimHarder(lock, owner): 
     15def claimHarder(lock, owner, la): 
    1616    """Return a Deferred that will fire when the lock is claimed. Keep trying 
    1717    until we succeed.""" 
    18     if lock.isAvailable(): 
     18    if lock.isAvailable(la): 
    1919        #print "claimHarder(%s): claiming" % owner 
    20         lock.claim(owner
     20        lock.claim(owner, la
    2121        return defer.succeed(lock) 
    2222    #print "claimHarder(%s): waiting" % owner 
    23     d = lock.waitUntilMaybeAvailable(owner
    24     d.addCallback(claimHarder, owner
     23    d = lock.waitUntilMaybeAvailable(owner, la
     24    d.addCallback(claimHarder, owner, la
    2525    return d 
    2626 
    27 def hold(lock, owner, mode="now"): 
     27def hold(lock, owner, la, mode="now"): 
    2828    if mode == "now": 
    29         lock.release(owner
     29        lock.release(owner, la
    3030    elif mode == "very soon": 
    31         reactor.callLater(0, lock.release, owner
     31        reactor.callLater(0, lock.release, owner, la
    3232    elif mode == "soon": 
    33         reactor.callLater(0.1, lock.release, owner) 
    34  
     33        reactor.callLater(0.1, lock.release, owner, la) 
    3534 
    3635class Unit(unittest.TestCase): 
    37     def testNow(self): 
     36    def testNowCounting(self): 
     37        lid = locks.MasterLock('dummy') 
     38        la = locks.LockAccess(lid, 'counting') 
     39        return self._testNow(la) 
     40 
     41    def testNowExclusive(self): 
     42        lid = locks.MasterLock('dummy') 
     43        la = locks.LockAccess(lid, 'exclusive') 
     44        return self._testNow(la) 
     45 
     46    def _testNow(self, la): 
    3847        l = locks.BaseLock("name") 
    39         self.failUnless(l.isAvailable()) 
    40         l.claim("owner1") 
    41         self.failIf(l.isAvailable()) 
    42         l.release("owner1") 
    43         self.failUnless(l.isAvailable()) 
    44  
    45     def testLater(self): 
    46         lock = locks.BaseLock("name") 
    47         d = claimHarder(lock, "owner1") 
    48         d.addCallback(lambda lock: lock.release("owner1")) 
    49         return d 
    50  
    51     def testCompetition(self): 
    52         lock = locks.BaseLock("name") 
    53         d = claimHarder(lock, "owner1") 
    54         d.addCallback(self._claim1) 
    55         return d 
    56     def _claim1(self, lock): 
     48        self.failUnless(l.isAvailable(la)) 
     49        l.claim("owner1", la) 
     50        self.failIf(l.isAvailable(la)) 
     51        l.release("owner1", la) 
     52        self.failUnless(l.isAvailable(la)) 
     53 
     54    def testNowMixed1(self): 
     55        """ Test exclusive is not possible when a counting has the lock """ 
     56        lid = locks.MasterLock('dummy') 
     57        lac = locks.LockAccess(lid, 'counting') 
     58        lae = locks.LockAccess(lid, 'exclusive') 
     59        l = locks.BaseLock("name", maxCount=2) 
     60        self.failUnless(l.isAvailable(lac)) 
     61        l.claim("count-owner", lac) 
     62        self.failIf(l.isAvailable(lae)) 
     63        l.release("count-owner", lac) 
     64        self.failUnless(l.isAvailable(lac)) 
     65 
     66    def testNowMixed2(self): 
     67        """ Test counting is not possible when an exclsuive has the lock """ 
     68        lid = locks.MasterLock('dummy') 
     69        lac = locks.LockAccess(lid, 'counting') 
     70        lae = locks.LockAccess(lid, 'exclusive') 
     71        l = locks.BaseLock("name", maxCount=2) 
     72        self.failUnless(l.isAvailable(lae)) 
     73        l.claim("count-owner", lae) 
     74        self.failIf(l.isAvailable(lac)) 
     75        l.release("count-owner", lae) 
     76        self.failUnless(l.isAvailable(lae)) 
     77 
     78    def testLaterCounting(self): 
     79        lid = locks.MasterLock('dummy') 
     80        la = locks.LockAccess(lid, 'counting') 
     81        return self._testLater(la) 
     82 
     83    def testLaterExclusive(self): 
     84        lid = locks.MasterLock('dummy') 
     85        la = locks.LockAccess(lid, 'exclusive') 
     86        return self._testLater(la) 
     87 
     88    def _testLater(self, la): 
     89        lock = locks.BaseLock("name") 
     90        d = claimHarder(lock, "owner1", la) 
     91        d.addCallback(lambda lock: lock.release("owner1", la)) 
     92        return d 
     93 
     94    def testCompetitionCounting(self): 
     95        lid = locks.MasterLock('dummy') 
     96        la = locks.LockAccess(lid, 'counting') 
     97        return self._testCompetition(la) 
     98 
     99    def testCompetitionExclusive(self): 
     100        lid = locks.MasterLock('dummy') 
     101        la = locks.LockAccess(lid, 'exclusive') 
     102        return self._testCompetition(la) 
     103 
     104    def _testCompetition(self, la): 
     105        lock = locks.BaseLock("name") 
     106        d = claimHarder(lock, "owner1", la) 
     107        d.addCallback(self._claim1, la) 
     108        return d 
     109    def _claim1(self, lock, la): 
    57110        # we should have claimed it by now 
    58         self.failIf(lock.isAvailable()) 
     111        self.failIf(lock.isAvailable(la)) 
    59112        # now set up two competing owners. We don't know which will get the 
    60113        # lock first. 
    61         d2 = claimHarder(lock, "owner2"
    62         d2.addCallback(hold, "owner2", "now") 
    63         d3 = claimHarder(lock, "owner3"
    64         d3.addCallback(hold, "owner3", "soon") 
     114        d2 = claimHarder(lock, "owner2", la
     115        d2.addCallback(hold, "owner2", la, "now") 
     116        d3 = claimHarder(lock, "owner3", la
     117        d3.addCallback(hold, "owner3", la, "soon") 
    65118        dl = defer.DeferredList([d2,d3]) 
    66         dl.addCallback(self._cleanup, lock
     119        dl.addCallback(self._cleanup, lock, la
    67120        # and release the lock in a moment 
    68         reactor.callLater(0.1, lock.release, "owner1"
     121        reactor.callLater(0.1, lock.release, "owner1", la
    69122        return dl 
    70123 
    71     def _cleanup(self, res, lock): 
    72         d = claimHarder(lock, "cleanup"
    73         d.addCallback(lambda lock: lock.release("cleanup")) 
     124    def _cleanup(self, res, lock, la): 
     125        d = claimHarder(lock, "cleanup", la
     126        d.addCallback(lambda lock: lock.release("cleanup", la)) 
    74127        return d 
    75128 
    76     def testRandom(self): 
     129    def testRandomCounting(self): 
     130        lid = locks.MasterLock('dummy') 
     131        la = locks.LockAccess(lid, 'counting') 
     132        return self._testRandom(la) 
     133 
     134    def testRandomExclusive(self): 
     135        lid = locks.MasterLock('dummy') 
     136        la = locks.LockAccess(lid, 'exclusive') 
     137        return self._testRandom(la) 
     138 
     139    def _testRandom(self, la): 
    77140        lock = locks.BaseLock("name") 
    78141        dl = [] 
    79142        for i in range(100): 
    80143            owner = "owner%d" % i 
    81144            mode = random.choice(["now", "very soon", "soon"]) 
    82             d = claimHarder(lock, owner
    83             d.addCallback(hold, owner, mode) 
     145            d = claimHarder(lock, owner, la
     146            d.addCallback(hold, owner, la, mode) 
    84147            dl.append(d) 
    85148        d = defer.DeferredList(dl) 
    86         d.addCallback(self._cleanup, lock
     149        d.addCallback(self._cleanup, lock, la
    87150        return d 
    88151 
    89152class Multi(unittest.TestCase): 
    90     def testNow(self): 
     153    def testNowCounting(self): 
     154        lid = locks.MasterLock('dummy') 
     155        la = locks.LockAccess(lid, 'counting') 
    91156        lock = locks.BaseLock("name", 2) 
    92         self.failUnless(lock.isAvailable()) 
    93         lock.claim("owner1"
    94         self.failUnless(lock.isAvailable()) 
    95         lock.claim("owner2"
    96         self.failIf(lock.isAvailable()) 
    97         lock.release("owner1"
    98         self.failUnless(lock.isAvailable()) 
    99         lock.release("owner2"
    100         self.failUnless(lock.isAvailable()) 
     157        self.failUnless(lock.isAvailable(la)) 
     158        lock.claim("owner1", la
     159        self.failUnless(lock.isAvailable(la)) 
     160        lock.claim("owner2", la
     161        self.failIf(lock.isAvailable(la)) 
     162        lock.release("owner1", la
     163        self.failUnless(lock.isAvailable(la)) 
     164        lock.release("owner2", la
     165        self.failUnless(lock.isAvailable(la)) 
    101166 
    102     def testLater(self): 
     167    def testLaterCounting(self): 
     168        lid = locks.MasterLock('dummy') 
     169        la = locks.LockAccess(lid, 'counting') 
    103170        lock = locks.BaseLock("name", 2) 
    104         lock.claim("owner1"
    105         lock.claim("owner2"
    106         d = claimHarder(lock, "owner3"
    107         d.addCallback(lambda lock: lock.release("owner3")) 
    108         lock.release("owner2"
    109         lock.release("owner1"
     171        lock.claim("owner1", la
     172        lock.claim("owner2", la
     173        d = claimHarder(lock, "owner3", la
     174        d.addCallback(lambda lock: lock.release("owner3", la)) 
     175        lock.release("owner2", la
     176        lock.release("owner1", la
    110177        return d 
    111178 
    112     def _cleanup(self, res, lock, count): 
     179    def _cleanup(self, res, lock, count, la): 
    113180        dl = [] 
    114181        for i in range(count): 
    115             d = claimHarder(lock, "cleanup%d" % i
     182            d = claimHarder(lock, "cleanup%d" % i, la
    116183            dl.append(d) 
    117184        d2 = defer.DeferredList(dl) 
    118185        # once all locks are claimed, we know that any previous owners have 
    119186        # been flushed out 
    120187        def _release(res): 
    121188            for i in range(count): 
    122                 lock.release("cleanup%d" % i
     189                lock.release("cleanup%d" % i, la
    123190        d2.addCallback(_release) 
    124191        return d2 
    125192 
    126     def testRandom(self): 
     193    def testRandomCounting(self): 
     194        lid = locks.MasterLock('dummy') 
     195        la = locks.LockAccess(lid, 'counting') 
    127196        COUNT = 5 
    128197        lock = locks.BaseLock("name", COUNT) 
    129198        dl = [] 
    130199        for i in range(100): 
    131200            owner = "owner%d" % i 
    132201            mode = random.choice(["now", "very soon", "soon"]) 
    133             d = claimHarder(lock, owner
     202            d = claimHarder(lock, owner, la
    134203            def _check(lock): 
    135204                self.failIf(len(lock.owners) > COUNT) 
    136205                return lock 
    137206            d.addCallback(_check) 
    138             d.addCallback(hold, owner, mode) 
     207            d.addCallback(hold, owner, la, mode) 
    139208            dl.append(d) 
    140209        d = defer.DeferredList(dl) 
    141         d.addCallback(self._cleanup, lock, COUNT
     210        d.addCallback(self._cleanup, lock, COUNT, la
    142211        return d 
    143212 
    144213class Dummy: 
  • buildbot-0.7.7/docs/buildbot.texinfo

    old new  
    54235423@cindex locks 
    54245424@slindex buildbot.locks.MasterLock 
    54255425@slindex buildbot.locks.SlaveLock 
     5426@slindex buildbot.locks.LockAccess 
    54265427 
    54275428For various reasons, you may want to prevent certain Steps (or perhaps 
    54285429entire Builds) from running simultaneously. Limited CPU speed or 
     
    54425443one-Build-at-a-time limit for each machine, but still allow as many 
    54435444simultaneous builds as there are machines. 
    54445445 
     5446When using a @code{Lock}, you can specify how you want to access it 
     5447with a @code{LockAccess} object. You can have an exclusive lock (the 
     5448@code{'exclusive'} mode) where the lock has exactly one owner, or 
     5449share it with with others up to the limit (the @code{'counting'} 
     5450mode). 
     5451 
    54455452Each @code{Lock} is created with a unique name. Each lock gets a count 
    5446 of how many owners it may have: how many processes can claim it at the 
     5453of how many owners it may have in @code{'counting'} mode: how many processes can claim it at the 
    54475454same time. This limit defaults to one, and is controllable through the 
    54485455@code{maxCount} argument. On @code{SlaveLock}s you can set the owner 
    54495456count on a per-slave basis by providing a dictionary (that maps from 
     
    54515458argument. Any buildslaves that aren't mentioned in 
    54525459@code{maxCountForSlave} get their owner count from @code{maxCount}. 
    54535460 
    5454 To use a lock, simply include it in the @code{locks=} argument of the 
     5461To use a lock, simply include it with the desired mode of access in the @code{locks=} argument of the 
    54555462@code{BuildStep} object that should obtain the lock before it runs. 
    5456 This argument accepts a list of @code{Lock} objects: the Step will 
    5457 acquire all of them before it runs. 
     5463This argument accepts a list of @code{LockAccess} objects: the Step will 
     5464acquire all of them before it runs. (For backwards compatibility, the 
     5465list also accepts @code{Lock} objects which are accessed in 
     5466@code{'counting'} mode.) 
    54585467 
    54595468To claim a lock for the whole Build, add a @code{'locks'} key to the 
    5460 builder specification dictionary with the same list of @code{Lock} 
    5461 objects. (This is the dictionary that has the @code{'name'}, 
     5469builder specification dictionary with the same list of 
     5470@code{LockAccess} objects (and for backwards compatibility, the list 
     5471also accepts @code{Lock} objects which are accessed in 
     5472@code{'counting'} mode). 
     5473(The builder specification dictionary is the dictionary that has the @code{'name'}, 
    54625474@code{'slavename'}, @code{'builddir'}, and @code{'factory'} keys). The 
    54635475@code{Build} object also accepts a @code{locks=} argument, but unless 
    54645476you are writing your own @code{BuildFactory} subclass then it will be 
     
    54925504f = factory.BuildFactory() 
    54935505f.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
    54945506f.addStep(shell.ShellCommand(command="make all")) 
    5495 f.addStep(shell.ShellCommand(command="make test", locks=[db_lock])) 
     5507f.addStep(shell.ShellCommand(command="make test", 
     5508                             locks=[db_lock.access('exclusive')])) 
    54965509b1 = @{'name': 'full1', 'slavename': 'bot-1', 'builddir': 'f1', 'factory': f@} 
    54975510b2 = @{'name': 'full2', 'slavename': 'bot-2', 'builddir': 'f2', 'factory': f@} 
    54985511b3 = @{'name': 'full3', 'slavename': 'bot-3', 'builddir': 'f3', 'factory': f@} 
    54995512c['builders'] = [b1, b2, b3] 
    55005513@end example 
    55015514 
     5515Note that by default, a @code{Lock} uses a @code{maxCount=1} limit, so 
     5516you can also use the @code{'counting'} access mode in this example. 
     5517 
    55025518In the next example, we have one buildslave hosting three separate 
    55035519Builders (each running tests against a different version of Python). 
    55045520The machine which hosts this buildslave is not particularly fast, so 
     
    55235539f23 = factory.Trial(source, trialpython=["python2.3"]) 
    55245540f24 = factory.Trial(source, trialpython=["python2.4"]) 
    55255541b1 = @{'name': 'p22', 'slavename': 'bot-1', 'builddir': 'p22', 
    5526       'factory': f22, 'locks': [slow_lock] @} 
     5542      'factory': f22, 'locks': [slow_lock.access('counting')] @} 
    55275543b2 = @{'name': 'p23', 'slavename': 'bot-1', 'builddir': 'p23', 
    5528       'factory': f23, 'locks': [slow_lock] @} 
     5544      'factory': f23, 'locks': [slow_lock.access('counting')] @} 
    55295545b3 = @{'name': 'p24', 'slavename': 'bot-1', 'builddir': 'p24', 
    5530       'factory': f24, 'locks': [slow_lock] @} 
     5546      'factory': f24, 'locks': [slow_lock.access('counting')] @} 
    55315547c['builders'] = [b1, b2, b3] 
    55325548@end example 
    55335549 
     5550You can also mix access modes. In the next example, a limit of at most 
     5551two builds is enforced. The cleanup process however needs exclusive 
     5552access to prevent interference with the build processes. 
     5553 
     5554@example 
     5555from buildbot import locks 
     5556from buildbot.steps import source, shell 
     5557from buildbot.process import factory 
     5558 
     5559build_lock = locks.MasterLock("builds", maxCount=2) 
     5560fb = factory.BuildFactory() 
     5561fb.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
     5562fb.addStep(shell.ShellCommand(command=["make", "all"])) 
     5563b1 = @{'name': 'b1', 'slavename': 'bot-1', 'builddir' : 'b1', 
     5564      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5565b2 = @{'name': 'b2', 'slavename': 'bot-2', 'builddir' : 'b2', 
     5566      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5567b3 = @{'name': 'b3', 'slavename': 'bot-3', 'builddir' : 'b3', 
     5568      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5569fc = factory.BuildFactory() 
     5570fc.addStep(shell.ShellCommand(command=["cleanup"])) 
     5571cl = @{'name': 'clean', 'slavename': 'bot-1', 'builddir' :clean', 
     5572      'factory': fc, 'locks': [build_lock.access('exclusive')] @} 
     5573c['builders'] = [b1, b2, b3, cl] 
     5574@end example 
     5575 
    55345576In the last example, we use two Locks at the same time. In this case, 
    55355577we're concerned about both of the previous constraints, but we'll say 
    55365578that only the tests are computationally intensive, and that they have 
     
    55515593cpu_lock = locks.SlaveLock("cpu", maxCountForSlave=slavecounts) 
    55525594f = factory.BuildFactory() 
    55535595f.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
    5554 f.addStep(shell.ShellCommand(command="make all", locks=[cpu_lock])) 
    5555 f.addStep(shell.ShellCommand(command="make test", locks=[cpu_lock])) 
    5556 f.addStep(shell.ShellCommand(command="make db-test", locks=[db_lock, cpu_lock])) 
     5596f.addStep(shell.ShellCommand(command="make all", 
     5597                             locks=[cpu_lock.access('counting')])) 
     5598f.addStep(shell.ShellCommand(command="make test", 
     5599                             locks=[cpu_lock.access('counting')])) 
     5600f.addStep(shell.ShellCommand(command="make db-test", 
     5601                             locks=[db_lock.access('exclusive'), 
     5602                                    cpu_lock.access('counting')])) 
    55575603 
    55585604b1 = @{'name': 'full1', 'slavename': 'bot-slow', 'builddir': 'full1', 
    55595605      'factory': f@}