diff options
-rwxr-xr-x | Demo/pdist/cvslock.py | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/Demo/pdist/cvslock.py b/Demo/pdist/cvslock.py new file mode 100755 index 0000000..a421e1a --- /dev/null +++ b/Demo/pdist/cvslock.py @@ -0,0 +1,280 @@ +"""CVS locking algorithm. + +CVS locking strategy +==================== + +As reverse engineered from the CVS 1.3 sources (file lock.c): + +- Locking is done on a per repository basis (but a process can hold +write locks for multiple directories); all lock files are placed in +the repository and have names beginning with "#cvs.". + +- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created +(and removed again), to test that we can write the repository. [The +algorithm can still be fooled (1) if the repository's mode is changed +while attempting to lock; (2) if this file exists and is writable but +the directory is not.] + +- While creating the actual read/write lock files (which may exist for +a long time), a "meta-lock" is held. The meta-lock is a directory +named "#cvs.lock" in the repository. The meta-lock is also held while +a write lock is held. + +- To set a read lock: + + - acquire the meta-lock + - create the file "#cvs.rfl.<pid>" + - release the meta-lock + +- To set a write lock: + + - acquire the meta-lock + - check that there are no files called "#cvs.rfl.*" + - if there are, release the meta-lock, sleep, try again + - create the file "#cvs.wfl.<pid>" + +- To release a write lock: + + - remove the file "#cvs.wfl.<pid>" + - rmdir the meta-lock + +- To release a read lock: + + - remove the file "#cvs.rfl.<pid>" + + +Additional notes +---------------- + +- A process should read-lock at most one repository at a time. + +- A process may write-lock as many repositories as it wishes (to avoid +deadlocks, I presume it should always lock them top-down in the +directory hierarchy). + +- A process should make sure it removes all its lock files and +directories when it crashes. + +- Limitation: one user id should not be committing files into the same +repository at the same time. + + +Turn this into Python code +-------------------------- + +rl = ReadLock(repository, waittime) + +wl = WriteLock(repository, waittime) + +list = MultipleWriteLock([repository1, repository2, ...], waittime) + +""" + + +import os +import time +import stat +import pwd + + +# Default wait time +DELAY = 10 + + +# XXX This should be the same on all Unix versions +EEXIST = 17 + + +# Files used for locking (must match cvs.h in the CVS sources) +CVSLCK = "#cvs.lck" +CVSRFL = "#cvs.rfl." +CVSWFL = "#cvs.wfl." + + +class Error: + + def __init__(self, msg): + self.msg = msg + + def __repr__(self): + return repr(self.msg) + + def __str__(self): + return str(self.msg) + + +class Locked(Error): + pass + + +class Lock: + + def __init__(self, repository = ".", delay = DELAY): + self.repository = repository + self.delay = delay + self.lockdir = None + self.lockfile = None + pid = `os.getpid()` + self.cvslck = self.join(CVSLCK) + self.cvsrfl = self.join(CVSRFL + pid) + self.cvswfl = self.join(CVSWFL + pid) + + def __del__(self): + print "__del__" + self.unlock() + + def setlockdir(self): + while 1: + try: + self.lockdir = self.cvslck + os.mkdir(self.cvslck, 0777) + return + except os.error, msg: + self.lockdir = None + if msg[0] == EEXIST: + try: + st = os.stat(self.cvslck) + except os.error: + continue + self.sleep(st) + continue + raise Error("failed to lock %s: %s" % ( + self.repository, msg)) + + def unlock(self): + self.unlockfile() + self.unlockdir() + + def unlockfile(self): + if self.lockfile: + print "unlink", self.lockfile + try: + os.unlink(self.lockfile) + except os.error: + pass + self.lockfile = None + + def unlockdir(self): + if self.lockdir: + print "rmdir", self.lockdir + try: + os.rmdir(self.lockdir) + except os.error: + pass + self.lockdir = None + + def sleep(self, st): + sleep(st, self.repository, self.delay) + + def join(self, name): + return os.path.join(self.repository, name) + + +def sleep(st, repository, delay): + if delay <= 0: + raise Locked(st) + uid = st[stat.ST_UID] + try: + pwent = pwd.getpwuid(uid) + user = pwent[0] + except KeyError: + user = "uid %d" % uid + print "[%s]" % time.ctime(time.time())[11:19], + print "Waiting for %s's lock in" % user, repository + time.sleep(delay) + + +class ReadLock(Lock): + + def __init__(self, repository, delay = DELAY): + Lock.__init__(self, repository, delay) + ok = 0 + try: + self.setlockdir() + self.lockfile = self.cvsrfl + fp = open(self.lockfile, 'w') + fp.close() + ok = 1 + finally: + if not ok: + self.unlockfile() + self.unlockdir() + + +class WriteLock(Lock): + + def __init__(self, repository, delay = DELAY): + Lock.__init__(self, repository, delay) + self.setlockdir() + while 1: + uid = self.readers_exist() + if not uid: + break + self.unlockdir() + self.sleep(uid) + self.lockfile = self.cvswfl + fp = open(self.lockfile, 'w') + fp.close() + + def readers_exist(self): + n = len(CVSRFL) + for name in os.listdir(self.repository): + if name[:n] == CVSRFL: + try: + st = os.stat(self.join(name)) + except os.error: + continue + return st + return None + + +def MultipleWriteLock(repositories, delay = DELAY): + while 1: + locks = [] + for r in repositories: + try: + locks.append(WriteLock(r, 0)) + except Locked, instance: + del locks + break + else: + break + sleep(instance.msg, r, delay) + return list + + +def test(): + import sys + if sys.argv[1:]: + repository = sys.argv[1] + else: + repository = "." + rl = None + wl = None + try: + print "attempting write lock ..." + wl = WriteLock(repository) + print "got it." + wl.unlock() + print "attempting read lock ..." + rl = ReadLock(repository) + print "got it." + rl.unlock() + finally: + print [1] + sys.exc_traceback = None + print [2] + if rl: + rl.unlock() + print [3] + if wl: + wl.unlock() + print [4] + rl = None + print [5] + wl = None + print [6] + + +if __name__ == '__main__': + test() |