mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			280 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
"""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()
 |