Home | History | Annotate | Download | only in pdist
      1 """CVS locking algorithm.
      2 
      3 CVS locking strategy
      4 ====================
      5 
      6 As reverse engineered from the CVS 1.3 sources (file lock.c):
      7 
      8 - Locking is done on a per repository basis (but a process can hold
      9 write locks for multiple directories); all lock files are placed in
     10 the repository and have names beginning with "#cvs.".

     11 
     12 - Before even attempting to lock, a file "#cvs.tfl.<pid>" is created
     13 (and removed again), to test that we can write the repository.  [The
     14 algorithm can still be fooled (1) if the repository's mode is changed
     15 while attempting to lock; (2) if this file exists and is writable but
     16 the directory is not.]
     17 
     18 - While creating the actual read/write lock files (which may exist for
     19 a long time), a "meta-lock" is held.  The meta-lock is a directory
     20 named "#cvs.lock" in the repository.  The meta-lock is also held while
     21 a write lock is held.
     22 
     23 - To set a read lock:
     24 
     25         - acquire the meta-lock
     26         - create the file "#cvs.rfl.<pid>"
     27         - release the meta-lock
     28 
     29 - To set a write lock:
     30 
     31         - acquire the meta-lock
     32         - check that there are no files called "#cvs.rfl.*"
     33                 - if there are, release the meta-lock, sleep, try again
     34         - create the file "#cvs.wfl.<pid>"
     35 
     36 - To release a write lock:
     37 
     38         - remove the file "#cvs.wfl.<pid>"
     39         - rmdir the meta-lock
     40 
     41 - To release a read lock:
     42 
     43         - remove the file "#cvs.rfl.<pid>"
     44 
     45 
     46 Additional notes
     47 ----------------
     48 
     49 - A process should read-lock at most one repository at a time.
     50 
     51 - A process may write-lock as many repositories as it wishes (to avoid
     52 deadlocks, I presume it should always lock them top-down in the
     53 directory hierarchy).
     54 
     55 - A process should make sure it removes all its lock files and
     56 directories when it crashes.
     57 
     58 - Limitation: one user id should not be committing files into the same
     59 repository at the same time.
     60 
     61 
     62 Turn this into Python code
     63 --------------------------
     64 
     65 rl = ReadLock(repository, waittime)
     66 
     67 wl = WriteLock(repository, waittime)
     68 
     69 list = MultipleWriteLock([repository1, repository2, ...], waittime)
     70 
     71 """
     72 
     73 
     74 import os
     75 import time
     76 import stat
     77 import pwd
     78 
     79 
     80 # Default wait time
     81 DELAY = 10
     82 
     83 
     84 # XXX This should be the same on all Unix versions
     85 EEXIST = 17
     86 
     87 
     88 # Files used for locking (must match cvs.h in the CVS sources)
     89 CVSLCK = "#cvs.lck"
     90 CVSRFL = "#cvs.rfl."
     91 CVSWFL = "#cvs.wfl."
     92 
     93 
     94 class Error:
     95 
     96     def __init__(self, msg):
     97         self.msg = msg
     98 
     99     def __repr__(self):
    100         return repr(self.msg)
    101 
    102     def __str__(self):
    103         return str(self.msg)
    104 
    105 
    106 class Locked(Error):
    107     pass
    108 
    109 
    110 class Lock:
    111 
    112     def __init__(self, repository = ".", delay = DELAY):
    113         self.repository = repository
    114         self.delay = delay
    115         self.lockdir = None
    116         self.lockfile = None
    117         pid = repr(os.getpid())
    118         self.cvslck = self.join(CVSLCK)
    119         self.cvsrfl = self.join(CVSRFL + pid)
    120         self.cvswfl = self.join(CVSWFL + pid)
    121 
    122     def __del__(self):
    123         print "__del__"
    124         self.unlock()
    125 
    126     def setlockdir(self):
    127         while 1:
    128             try:
    129                 self.lockdir = self.cvslck
    130                 os.mkdir(self.cvslck, 0777)
    131                 return
    132             except os.error, msg:
    133                 self.lockdir = None
    134                 if msg[0] == EEXIST:
    135                     try:
    136                         st = os.stat(self.cvslck)
    137                     except os.error:
    138                         continue
    139                     self.sleep(st)
    140                     continue
    141                 raise Error("failed to lock %s: %s" % (
    142                         self.repository, msg))
    143 
    144     def unlock(self):
    145         self.unlockfile()
    146         self.unlockdir()
    147 
    148     def unlockfile(self):
    149         if self.lockfile:
    150             print "unlink", self.lockfile
    151             try:
    152                 os.unlink(self.lockfile)
    153             except os.error:
    154                 pass
    155             self.lockfile = None
    156 
    157     def unlockdir(self):
    158         if self.lockdir:
    159             print "rmdir", self.lockdir
    160             try:
    161                 os.rmdir(self.lockdir)
    162             except os.error:
    163                 pass
    164             self.lockdir = None
    165 
    166     def sleep(self, st):
    167         sleep(st, self.repository, self.delay)
    168 
    169     def join(self, name):
    170         return os.path.join(self.repository, name)
    171 
    172 
    173 def sleep(st, repository, delay):
    174     if delay <= 0:
    175         raise Locked(st)
    176     uid = st[stat.ST_UID]
    177     try:
    178         pwent = pwd.getpwuid(uid)
    179         user = pwent[0]
    180     except KeyError:
    181         user = "uid %d" % uid
    182     print "[%s]" % time.ctime(time.time())[11:19],
    183     print "Waiting for %s's lock in" % user, repository
    184     time.sleep(delay)
    185 
    186 
    187 class ReadLock(Lock):
    188 
    189     def __init__(self, repository, delay = DELAY):
    190         Lock.__init__(self, repository, delay)
    191         ok = 0
    192         try:
    193             self.setlockdir()
    194             self.lockfile = self.cvsrfl
    195             fp = open(self.lockfile, 'w')
    196             fp.close()
    197             ok = 1
    198         finally:
    199             if not ok:
    200                 self.unlockfile()
    201             self.unlockdir()
    202 
    203 
    204 class WriteLock(Lock):
    205 
    206     def __init__(self, repository, delay = DELAY):
    207         Lock.__init__(self, repository, delay)
    208         self.setlockdir()
    209         while 1:
    210             uid = self.readers_exist()
    211             if not uid:
    212                 break
    213             self.unlockdir()
    214             self.sleep(uid)
    215         self.lockfile = self.cvswfl
    216         fp = open(self.lockfile, 'w')
    217         fp.close()
    218 
    219     def readers_exist(self):
    220         n = len(CVSRFL)
    221         for name in os.listdir(self.repository):
    222             if name[:n] == CVSRFL:
    223                 try:
    224                     st = os.stat(self.join(name))
    225                 except os.error:
    226                     continue
    227                 return st
    228         return None
    229 
    230 
    231 def MultipleWriteLock(repositories, delay = DELAY):
    232     while 1:
    233         locks = []
    234         for r in repositories:
    235             try:
    236                 locks.append(WriteLock(r, 0))
    237             except Locked, instance:
    238                 del locks
    239                 break
    240         else:
    241             break
    242         sleep(instance.msg, r, delay)
    243     return list
    244 
    245 
    246 def test():
    247     import sys
    248     if sys.argv[1:]:
    249         repository = sys.argv[1]
    250     else:
    251         repository = "."
    252     rl = None
    253     wl = None
    254     try:
    255         print "attempting write lock ..."
    256         wl = WriteLock(repository)
    257         print "got it."
    258         wl.unlock()
    259         print "attempting read lock ..."
    260         rl = ReadLock(repository)
    261         print "got it."
    262         rl.unlock()
    263     finally:
    264         print [1]
    265         sys.exc_traceback = None
    266         print [2]
    267         if rl:
    268             rl.unlock()
    269         print [3]
    270         if wl:
    271             wl.unlock()
    272         print [4]
    273         rl = None
    274         print [5]
    275         wl = None
    276         print [6]
    277 
    278 
    279 if __name__ == '__main__':
    280     test()
    281