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