This file locking code is not really usable yet, at least not Windows. The lock file is not deleted there.
- Resources
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - File Locking Classes
4
5 File locking is used for interprocess/multithreaded access
6 to common files.
7
8 @copyright: 2005 by Alexander Schremmer <alex AT alexanderweb DOT de>
9 partly based on code by Barry A. Warsaw
10 @license: GNU GPL, see COPYING for details.
11 """
12
13 import os
14 import time
15 import random
16 import errno
17
18 # Exceptions that can be raised by this module
19 class LockError(Exception):
20 """Base class for all exceptions in this module."""
21
22 class AlreadyLockedError(LockError):
23 """An attempt is made to lock an already locked object."""
24
25 class NotLockedError(LockError):
26 """An attempt is made to unlock an object that isn't locked."""
27
28 class TimeOutError(LockError):
29 """The timeout interval elapsed before the lock succeeded."""
30
31 class FileLockBase:
32 def __init__(self, path):
33 self._path = path
34 self._islocked = False
35
36 def _sleep(self):
37 """
38 Used to sleep in a loop which tries to acquire a
39 lock in a blocking way.
40 """
41 interval = random.random() * 2.0 + 0.01
42 time.sleep(interval)
43
44 def isLocked(self):
45 return self._islocked
46
47 def close(self):
48 raise NotImplementedError
49
50 def lock(self, timeout=None):
51 """Acquire the lock
52
53 With no timeout, a blocking lock is acquired. A timeout is a hint in
54 floating seconds for the length of time to attempt to acquire the
55 lock. If no lock can be acquired during that time, a TimeOutError is
56 raised.
57 """
58 raise NotImplementedError
59
60 def unlock(self):
61 raise NotImplementedError
62
63 class FileLockDummy(FileLockBase):
64 def __init__(self, path):
65 FileLockBase.__init__(self, path)
66 self.isDummy = True # Allow duck typing :-)
67
68 def close(self):
69 pass
70
71 def lock(self, timeout=None):
72 self._islocked = True
73 pass
74
75 def unlock(self):
76 self._islocked = False
77 pass
78
79 class FileLockPosix(FileLockBase):
80 """
81 File locking used on Posix platforms.
82
83 Note that this implementation is not NFSv2-safe.
84 """
85
86 def __init__(self, path):
87 FileLockBase.__init__(self, path)
88 try:
89 self._fp = open(path, 'r+')
90 except IOError, e:
91 if e.errno <> errno.ENOENT:
92 raise
93 try:
94 self._fp = open(path, 'w+')
95 except:
96 print "OH OH"
97 raise
98
99 def __del__(self):
100 self.close()
101
102 def close(self):
103 if self._fp is not None:
104 if self._islocked:
105 self.unlock()
106 self._fp.close()
107 try:
108 os.unlink(self._path)
109 except OSError, e:
110 if e.errno <> errno.ENOENT:
111 raise
112 self._fp = None
113
114 def lock(self, timeout=None):
115 if self._islocked:
116 raise AlreadyLockedError
117 fileno = self._fp.fileno()
118 if timeout is None:
119 # block until we have the lock
120 fcntl.flock(fileno, fcntl.LOCK_EX)
121 self._islocked = True
122 return
123 expires = time.time() + timeout
124 while True:
125 try:
126 fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
127 self._islocked = True
128 break
129 except IOError, e:
130 if e.errno not in (errno.EAGAIN, errno.EACCES):
131 raise
132 if time.time() > expires:
133 raise TimeOutError
134 self._sleep()
135
136 def unlock(self):
137 if not self._islocked:
138 raise NotLockedError
139 fcntl.flock(self._fp.fileno(), fcntl.LOCK_UN)
140 self._islocked = False
141
142
143 class FileLockWindows(FileLockBase):
144 """
145 File locking used on Windows.
146 """
147
148 # the range of bytes to be locked
149 _lockrange = 2147483647
150
151 def __init__(self, path):
152 FileLockBase.__init__(self, path)
153 try:
154 self._fp = open(path, 'r+')
155 except IOError, e:
156 if e.errno <> errno.ENOENT: raise
157 self._fp = open(path, 'w+')
158
159 def __del__(self):
160 self.close()
161
162 def close(self):
163 if self._fp is not None:
164 if self._islocked:
165 self.unlock()
166 self._fp.close()
167 self._fp = None
168
169 def lock(self, timeout=None):
170 if self._islocked:
171 raise AlreadyLockedError
172 fileno = self._fp.fileno()
173 if timeout is None:
174 # block until we have the lock
175 try:
176 msvcrt.locking(fileno, msvcrt.LK_LOCK, FileLockWindows._lockrange)
177 except IOError, e:
178 if e.errno == errno.EDEADLOCK:
179 raise TimeOutError
180 else:
181 raise
182 else:
183 self._islocked = True
184 return
185 expires = time.time() + timeout
186 while True:
187 try:
188 msvcrt.locking(fileno, msvcrt.LK_NBLCK, FileLockWindows._lockrange)
189 self._islocked = True
190 break
191 except IOError, e:
192 if e.errno not in (errno.EAGAIN, errno.EACCES):
193 raise
194 if time.time() > expires:
195 raise TimeOutError
196 self._sleep()
197
198 def unlock(self):
199 if not self._islocked:
200 raise NotLockedError
201 msvcrt.locking(self._fp.fileno(), msvcrt.LK_UNLCK, FileLockWindows._lockrange)
202 self._islocked = False
203
204
205 # Set the right locking class
206
207 if os.name == 'posix':
208 import fcntl
209 LockFile = FileLockPosix
210 elif os.name == 'nt':
211 import msvcrt
212 LockFile = FileLockWindows
213 else:
214 LockFile = FileLockDummy
215 import warnings
216 warnings.warn("filelocking.py: Your platform is not supported by the"
217 " file locking code. Data corruptions might happen.\n")
218
219 # Multi-process stress tests
220 def _dochild(childno):
221 prefix = '[%d]' % childno
222 # Create somewhere between 1 and 1000 locks
223 lockfile = LockFile('LockTest')
224 workinterval = 3 * random.random()
225 hitwait = 10 * random.random()
226 print prefix, 'workinterval:', workinterval
227 islocked = False
228 t0 = 0
229 t1 = 0
230 t2 = 0
231 try:
232 try:
233 t0 = time.time()
234 print prefix, 'acquiring...'
235 lockfile.lock()
236 print prefix, 'acquired...'
237 islocked = True
238 except TimeOutError:
239 print prefix, 'timed out - ERR'
240 else:
241 t1 = time.time()
242 print prefix, 'acquisition time:', t1-t0, 'sec'
243 time.sleep(workinterval)
244 finally:
245 if islocked:
246 try:
247 lockfile.close()
248 t2 = time.time()
249 print prefix, 'released; lock hold time:', t2-t1, 'secs'
250 except NotLockedError:
251 print prefix, 'lock was broken - ERR'
252 # wait for next web hit
253 print prefix, 'sleep:', hitwait
254 time.sleep(hitwait)
255
256
257 def _seed():
258 d = sha.new(`os.getpid()`+`time.time()`).hexdigest()
259 random.seed(d)
260
261 def _test_thread(numtests):
262 import threading
263 kids = []
264 for childno in range(numtests):
265 pid = threading.Thread(target=_testthread2, args=(childno,))
266 kids.append(pid)
267 pid.start()
268 while kids:
269 kids[0].join()
270 del kids[0]
271
272 def _testthread2(childno):
273 import thread
274 # child
275 _seed()
276 try:
277 loopcount = random.randint(1, 10)
278 for i in range(loopcount):
279 print '[%d] Loop %d of %d' % (childno, i+1, loopcount)
280 _dochild(childno)
281 except KeyboardInterrupt:
282 pass
283 thread.exit()
284
285 def _test_fork(numtests):
286 kids = {}
287 for childno in range(numtests):
288 pid = os.fork()
289 if pid:
290 # parent
291 kids[pid] = pid
292 else:
293 # child
294 _seed()
295 try:
296 loopcount = random.randint(1, 10)
297 for i in range(loopcount):
298 print '[%d] Loop %d of %d' % (childno, i+1, loopcount)
299 pid = os.fork()
300 if pid:
301 # parent, wait for child to exit
302 pid, status = os.waitpid(pid, 0)
303 else:
304 # child
305 _seed()
306 try:
307 _dochild(childno)
308 except KeyboardInterrupt:
309 pass
310 os._exit(0)
311 except KeyboardInterrupt:
312 pass
313 os._exit(0)
314 while kids:
315 pid, status = os.waitpid(-1, os.WNOHANG)
316 if pid <> 0:
317 del kids[pid]
318
319 if __name__ == '__main__':
320 import sha, sys, time, random
321
322 sys.argv.insert(1,"10")
323 {'nt': _test_thread,
324 'posix': _test_fork,
325 }[os.name](int(sys.argv[1]))