Changeset 709

Show
Ignore:
Timestamp:
09/05/08 17:04:04 (4 months ago)
Author:
dustin@zmanda.com
Message:

add build access modes

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • buildbot/locks.py

    r708 r709  
    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 
    38  
    39     def claim(self, owner): 
     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): 
    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 
     
    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 
     
    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} 
     
    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 
  • buildbot/master.py

    r708 r709  
    642642        for b in builders: 
    643643            for l in b.get('locks', []): 
     644                if isinstance(l, locks.LockAccess): # User specified access to the lock 
     645                    l = l.lockid 
    644646                if lock_dict.has_key(l.name): 
    645647                    if lock_dict[l.name] is not l: 
     
    654656            for s in b['factory'].steps: 
    655657                for l in s[1].get('locks', []): 
     658                    if isinstance(l, locks.LockAccess): # User specified access to the lock 
     659                        l = l.lockid 
    656660                    if lock_dict.has_key(l.name): 
    657661                        if lock_dict[l.name] is not l: 
  • buildbot/process/base.py

    r653 r709  
    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 
     
    313313 
    314314        # convert all locks into their real forms 
    315         self.locks = [self.builder.botmaster.getLockByID(l) 
    316                       for l in self.locks] 
     315        lock_list = [] 
     316        for access in self.locks: 
     317            if not isinstance(access, locks.LockAccess): 
     318                # Buildbot 0.7.7 compability: user did not specify access 
     319                access = access.defaultAccess() 
     320            lock = self.builder.botmaster.getLockByID(access.lockid) 
     321            lock_list.append((lock, access)) 
     322        self.locks = lock_list 
    317323        # then narrow SlaveLocks down to the right slave 
    318         self.locks = [l.getLock(self.slavebuilder) for l in self.locks] 
     324        self.locks = [(l.getLock(self.slavebuilder), la) 
     325                       for l, la in self.locks] 
    319326        self.remote = slavebuilder.remote 
    320327        self.remote.notifyOnDisconnect(self.lostRemote) 
     
    352359        if not self.locks: 
    353360            return defer.succeed(None) 
    354         for lock in self.locks: 
    355             if not lock.isAvailable(): 
     361        for lock, access in self.locks: 
     362            if not lock.isAvailable(access): 
    356363                log.msg("Build %s waiting for lock %s" % (self, lock)) 
    357                 d = lock.waitUntilMaybeAvailable(self
     364                d = lock.waitUntilMaybeAvailable(self, access
    358365                d.addCallback(self.acquireLocks) 
    359366                return d 
    360367        # all locks are available, claim them all 
    361         for lock in self.locks: 
    362             lock.claim(self
     368        for lock, access in self.locks: 
     369            lock.claim(self, access
    363370        return defer.succeed(None) 
    364371 
     
    585592    def releaseLocks(self): 
    586593        log.msg("releaseLocks(%s): %s" % (self, self.locks)) 
    587         for lock in self.locks: 
    588             lock.release(self
     594        for lock, access in self.locks: 
     595            lock.release(self, access
    589596 
    590597    # IBuildControl 
  • buildbot/process/buildstep.py

    r677 r709  
    99from twisted.web.util import formatFailure 
    1010 
    11 from buildbot import interfaces 
     11from buildbot import interfaces, locks 
    1212from buildbot.status import progress 
    1313from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \ 
     
    663663        self.deferred = defer.Deferred() 
    664664        # convert all locks into their real form 
    665         self.locks = [self.build.builder.botmaster.getLockByID(l) 
    666                       for l in self.locks] 
     665        lock_list = [] 
     666        for access in self.locks: 
     667            if not isinstance(access, locks.LockAccess): 
     668                # Buildbot 0.7.7 compability: user did not specify access 
     669                access = access.defaultAccess() 
     670            lock = self.build.builder.botmaster.getLockByID(access.lockid) 
     671            lock_list.append((lock, access)) 
     672        self.locks = lock_list 
    667673        # then narrow SlaveLocks down to the slave that this build is being 
    668674        # run on 
    669         self.locks = [l.getLock(self.build.slavebuilder) for l in self.locks] 
    670         for l in self.locks: 
     675        self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks] 
     676        for l, la in self.locks: 
    671677            if l in self.build.locks: 
    672678                log.msg("Hey, lock %s is claimed by both a Step (%s) and the" 
     
    681687        if not self.locks: 
    682688            return defer.succeed(None) 
    683         for lock in self.locks: 
    684             if not lock.isAvailable(): 
     689        for lock, access in self.locks: 
     690            if not lock.isAvailable(access): 
    685691                log.msg("step %s waiting for lock %s" % (self, lock)) 
    686                 d = lock.waitUntilMaybeAvailable(self
     692                d = lock.waitUntilMaybeAvailable(self, access
    687693                d.addCallback(self.acquireLocks) 
    688694                return d 
    689695        # all locks are available, claim them all 
    690         for lock in self.locks: 
    691             lock.claim(self
     696        for lock, access in self.locks: 
     697            lock.claim(self, access
    692698        return defer.succeed(None) 
    693699 
     
    770776    def releaseLocks(self): 
    771777        log.msg("releaseLocks(%s): %s" % (self, self.locks)) 
    772         for lock in self.locks: 
    773             lock.release(self
     778        for lock, access in self.locks: 
     779            lock.release(self, access
    774780 
    775781    def finished(self, results): 
  • buildbot/test/test_locks.py

    r408 r709  
    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): 
     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): 
    4689        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): 
     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): 
    52105        lock = locks.BaseLock("name") 
    53         d = claimHarder(lock, "owner1"
    54         d.addCallback(self._claim1
    55         return d 
    56     def _claim1(self, lock): 
     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")) 
    74         return d 
    75  
    76     def testRandom(self): 
     124    def _cleanup(self, res, lock, la): 
     125        d = claimHarder(lock, "cleanup", la) 
     126        d.addCallback(lambda lock: lock.release("cleanup", la)) 
     127        return d 
     128 
     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 = [] 
     
    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()) 
    101  
    102     def testLater(self): 
     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)) 
     166 
     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"
    110         return d 
    111  
    112     def _cleanup(self, res, lock, count): 
     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
     177        return d 
     178 
     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) 
     
    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) 
     
    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 
  • docs/buildbot.texinfo

    r708 r709  
    57875787@slindex buildbot.locks.MasterLock 
    57885788@slindex buildbot.locks.SlaveLock 
     5789@slindex buildbot.locks.LockAccess 
    57895790 
    57905791For various reasons, you may want to prevent certain Steps (or perhaps 
     
    58065807simultaneous builds as there are machines. 
    58075808 
     5809When using a @code{Lock}, you can specify how you want to access it 
     5810with a @code{LockAccess} object. You can have an exclusive lock (the 
     5811@code{'exclusive'} mode) where the lock has exactly one owner, or 
     5812share it with with others up to the limit (the @code{'counting'} 
     5813mode). 
     5814 
    58085815Each @code{Lock} is created with a unique name. Each lock gets a count 
    5809 of how many owners it may have: how many processes can claim it at the 
     5816of how many owners it may have in @code{'counting'} mode: how many processes can claim it at the 
    58105817same time. This limit defaults to one, and is controllable through the 
    58115818@code{maxCount} argument. On @code{SlaveLock}s you can set the owner 
     
    58155822@code{maxCountForSlave} get their owner count from @code{maxCount}. 
    58165823 
    5817 To use a lock, simply include it in the @code{locks=} argument of the 
     5824To use a lock, simply include it with the desired mode of access in the @code{locks=} argument of the 
    58185825@code{BuildStep} object that should obtain the lock before it runs. 
    5819 This argument accepts a list of @code{Lock} objects: the Step will 
    5820 acquire all of them before it runs. 
     5826This argument accepts a list of @code{LockAccess} objects: the Step will 
     5827acquire all of them before it runs. (For backwards compatibility, the 
     5828list also accepts @code{Lock} objects which are accessed in 
     5829@code{'counting'} mode.) 
    58215830 
    58225831To claim a lock for the whole Build, add a @code{'locks'} key to the 
    5823 builder specification dictionary with the same list of @code{Lock} 
    5824 objects. (This is the dictionary that has the @code{'name'}, 
     5832builder specification dictionary with the same list of 
     5833@code{LockAccess} objects (and for backwards compatibility, the list 
     5834also accepts @code{Lock} objects which are accessed in 
     5835@code{'counting'} mode). 
     5836(The builder specification dictionary is the dictionary that has the @code{'name'}, 
    58255837@code{'slavename'}, @code{'builddir'}, and @code{'factory'} keys). The 
    58265838@code{Build} object also accepts a @code{locks=} argument, but unless 
     
    58565868f.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
    58575869f.addStep(shell.ShellCommand(command="make all")) 
    5858 f.addStep(shell.ShellCommand(command="make test", locks=[db_lock])) 
     5870f.addStep(shell.ShellCommand(command="make test", 
     5871                             locks=[db_lock.access('exclusive')])) 
    58595872b1 = @{'name': 'full1', 'slavename': 'bot-1', 'builddir': 'f1', 'factory': f@} 
    58605873b2 = @{'name': 'full2', 'slavename': 'bot-2', 'builddir': 'f2', 'factory': f@} 
     
    58625875c['builders'] = [b1, b2, b3] 
    58635876@end example 
     5877 
     5878Note that by default, a @code{Lock} uses a @code{maxCount=1} limit, so 
     5879you can also use the @code{'counting'} access mode in this example. 
    58645880 
    58655881In the next example, we have one buildslave hosting three separate 
     
    58875903f24 = factory.Trial(source, trialpython=["python2.4"]) 
    58885904b1 = @{'name': 'p22', 'slavename': 'bot-1', 'builddir': 'p22', 
    5889       'factory': f22, 'locks': [slow_lock] @} 
     5905      'factory': f22, 'locks': [slow_lock.access('counting')] @} 
    58905906b2 = @{'name': 'p23', 'slavename': 'bot-1', 'builddir': 'p23', 
    5891       'factory': f23, 'locks': [slow_lock] @} 
     5907      'factory': f23, 'locks': [slow_lock.access('counting')] @} 
    58925908b3 = @{'name': 'p24', 'slavename': 'bot-1', 'builddir': 'p24', 
    5893       'factory': f24, 'locks': [slow_lock] @} 
     5909      'factory': f24, 'locks': [slow_lock.access('counting')] @} 
    58945910c['builders'] = [b1, b2, b3] 
     5911@end example 
     5912 
     5913You can also mix access modes. In the next example, a limit of at most 
     5914two builds is enforced. The cleanup process however needs exclusive 
     5915access to prevent interference with the build processes. 
     5916 
     5917@example 
     5918from buildbot import locks 
     5919from buildbot.steps import source, shell 
     5920from buildbot.process import factory 
     5921 
     5922build_lock = locks.MasterLock("builds", maxCount=2) 
     5923fb = factory.BuildFactory() 
     5924fb.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
     5925fb.addStep(shell.ShellCommand(command=["make", "all"])) 
     5926b1 = @{'name': 'b1', 'slavename': 'bot-1', 'builddir' : 'b1', 
     5927      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5928b2 = @{'name': 'b2', 'slavename': 'bot-2', 'builddir' : 'b2', 
     5929      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5930b3 = @{'name': 'b3', 'slavename': 'bot-3', 'builddir' : 'b3', 
     5931      'factory': fb, 'locks': [build_lock.access('counting')] @} 
     5932fc = factory.BuildFactory() 
     5933fc.addStep(shell.ShellCommand(command=["cleanup"])) 
     5934cl = @{'name': 'clean', 'slavename': 'bot-1', 'builddir' :clean', 
     5935      'factory': fc, 'locks': [build_lock.access('exclusive')] @} 
     5936c['builders'] = [b1, b2, b3, cl] 
    58955937@end example 
    58965938 
     
    59155957f = factory.BuildFactory() 
    59165958f.addStep(source.SVN(svnurl="http://example.org/svn/Trunk")) 
    5917 f.addStep(shell.ShellCommand(command="make all", locks=[cpu_lock])) 
    5918 f.addStep(shell.ShellCommand(command="make test", locks=[cpu_lock])) 
    5919 f.addStep(shell.ShellCommand(command="make db-test", locks=[db_lock, cpu_lock])) 
     5959f.addStep(shell.ShellCommand(command="make all", 
     5960                             locks=[cpu_lock.access('counting')])) 
     5961f.addStep(shell.ShellCommand(command="make test", 
     5962                             locks=[cpu_lock.access('counting')])) 
     5963f.addStep(shell.ShellCommand(command="make db-test", 
     5964                             locks=[db_lock.access('exclusive'), 
     5965                                    cpu_lock.access('counting')])) 
    59205966 
    59215967b1 = @{'name': 'full1', 'slavename': 'bot-slow', 'builddir': 'full1',