1d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen"""CVS locking algorithm. 2d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 3d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenCVS locking strategy 4d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen==================== 5d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 6d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenAs reverse engineered from the CVS 1.3 sources (file lock.c): 7d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 8d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- Locking is done on a per repository basis (but a process can hold 9d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenwrite locks for multiple directories); all lock files are placed in 10d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenthe repository and have names beginning with "#cvs.". 11d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 12d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created 13d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen(and removed again), to test that we can write the repository. [The 14d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenalgorithm can still be fooled (1) if the repository's mode is changed 15d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenwhile attempting to lock; (2) if this file exists and is writable but 16d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenthe directory is not.] 17d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 18d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- While creating the actual read/write lock files (which may exist for 19d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthena long time), a "meta-lock" is held. The meta-lock is a directory 20d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthennamed "#cvs.lock" in the repository. The meta-lock is also held while 21d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthena write lock is held. 22d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 23d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- To set a read lock: 24d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 25d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - acquire the meta-lock 26d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - create the file "#cvs.rfl.<pid>" 27d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - release the meta-lock 28d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 29d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- To set a write lock: 30d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 31d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - acquire the meta-lock 32d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - check that there are no files called "#cvs.rfl.*" 33d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - if there are, release the meta-lock, sleep, try again 34d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - create the file "#cvs.wfl.<pid>" 35d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 36d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- To release a write lock: 37d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 38d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - remove the file "#cvs.wfl.<pid>" 39d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - rmdir the meta-lock 40d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 41d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- To release a read lock: 42d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 43d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen - remove the file "#cvs.rfl.<pid>" 44d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 45d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 46d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenAdditional notes 47d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen---------------- 48d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 49d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- A process should read-lock at most one repository at a time. 50d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 51d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- A process may write-lock as many repositories as it wishes (to avoid 52d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthendeadlocks, I presume it should always lock them top-down in the 53d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthendirectory hierarchy). 54d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 55d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- A process should make sure it removes all its lock files and 56d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthendirectories when it crashes. 57d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 58d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen- Limitation: one user id should not be committing files into the same 59d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenrepository at the same time. 60d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 61d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 62d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenTurn this into Python code 63d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen-------------------------- 64d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 65d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenrl = ReadLock(repository, waittime) 66d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 67d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenwl = WriteLock(repository, waittime) 68d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 69d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenlist = MultipleWriteLock([repository1, repository2, ...], waittime) 70d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 71d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen""" 72d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 73d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 74d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenimport os 75d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenimport time 76d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenimport stat 77d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenimport pwd 78d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 79d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 80d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen# Default wait time 81d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenDELAY = 10 82d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 83d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 84d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen# XXX This should be the same on all Unix versions 85d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenEEXIST = 17 86d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 87d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 88d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen# Files used for locking (must match cvs.h in the CVS sources) 89d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenCVSLCK = "#cvs.lck" 90d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenCVSRFL = "#cvs.rfl." 91d9c76c765496aab9b4495083b9c5c8a254eeb754David ZeuthenCVSWFL = "#cvs.wfl." 92d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 93d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 94d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenclass Error: 95d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 96d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen def __init__(self, msg): 97d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen self.msg = msg 98d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 99d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen def __repr__(self): 100d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen return repr(self.msg) 101d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 102d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen def __str__(self): 103d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen return str(self.msg) 104d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 105d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 106d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenclass Locked(Error): 107d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen pass 108d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 109d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 110d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthenclass Lock: 111d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 112d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen def __init__(self, repository = ".", delay = DELAY): 113d9c76c765496aab9b4495083b9c5c8a254eeb754David Zeuthen 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 173def 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 187class 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 204class 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 231def 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 246def 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 279if __name__ == '__main__': 280 test() 281