1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
# -*- coding: iso-8859-1 -*-
"""
MoinMoin - locking functions
@copyright: 2005 Florian Festi, Nir Soffer
@license: GNU GPL, see COPYING for details.
"""
import os, sys, tempfile, time, errno
class Timer:
""" Simple count down timer
Useful for code that needs to complete a task within some timeout.
"""
defaultSleep = 0.25
maxSleep = 0.25
def __init__(self, timeout):
self.setTimeout(timeout)
self._start = None
self._stop = None
def setTimeout(self, timeout):
self.timeout = timeout
if timeout is None:
self._sleep = self.defaultSleep
else:
self._sleep = min(timeout / 10.0, self.maxSleep)
def start(self):
""" Start the countdown """
if self.timeout is None:
return
now = time.time()
self._start = now
self._stop = now + self.timeout
def haveTime(self):
""" Check if timeout has not passed """
if self.timeout is None:
return True
return time.time() <= self._stop
def sleep(self):
""" Sleep without sleeping over timeout """
if self._stop is not None:
timeLeft = max(self._stop - time.time(), 0)
sleep = min(self._sleep, timeLeft)
else:
sleep = self._sleep
time.sleep(sleep)
def elapsed(self):
return time.time() - self._start
class ExclusiveLock:
""" Exclusive lock
Uses a directory as portable lock method. On all platforms,
creating a directory will fail if the directory exists.
Only one exclusive lock per resource is allowed. This lock is not
used directly by clients, but used by both ReadLock and WriteLock.
If created with a timeout, the lock will expire timeout seconds
after it has been acquired. Without a timeout, it will never expire.
"""
fileName = '' # The directory is the lockDir
timerClass = Timer
def __init__(self, dir, timeout=None):
""" Init a write lock
@param dir: the lock directory. Since this lock uses a empty
filename, the dir is the lockDir.
@param timeout: while trying to acquire, the lock will expire
other exclusive locks older than timeout.
WARNING: because of file system timing limitations, timeouts
must be at least 2 seconds.
"""
self.dir = dir
if timeout is not None and timeout < 2.0:
raise ValueError('timeout must be at least 2 seconds')
self.timeout = timeout
if self.fileName:
self.lockDir = os.path.join(dir, self.fileName)
self._makeDir()
else:
self.lockDir = dir
self._locked = False
def acquire(self, timeout=None):
""" Try to acquire a lock.
Try to create the lock directory. If it fails because another
lock exists, try to expire the other lock. Repeat after little
sleep until timeout passed.
Return True if a lock was acquired; False otherwise.
"""
timer = self.timerClass(timeout)
timer.start()
while timer.haveTime():
try:
os.mkdir(self.lockDir)
self._locked = True
# log('acquired exclusive lock: %s\n' % (self.lockDir, ))
return True
except OSError, err:
if err.errno != errno.EEXIST:
raise
if self.expire():
continue # Try immediately to acquire
timer.sleep()
return False
def release(self):
""" Release the lock """
if not self._locked:
raise RuntimeError("lock already released")
self._removeLockDir()
self._locked = False
# log('released lock: %s\n' % self.lockDir)
def isLocked(self):
return self._locked
def exists(self):
return os.path.exists(self.lockDir)
def isExpired(self):
""" Return True if too old or missing; False otherwise
TODO: Since stat returns times using whole seconds, this is
quite broken. Maybe use OS specific calls like Carbon.File on
Mac OS X?
"""
if self.timeout is None:
return not self.exists()
try:
lock_age = time.time() - os.stat(self.lockDir).st_mtime
return lock_age > self.timeout
except OSError, err:
if err.errno == errno.ENOENT:
# No such lock file, therefore "expired"
return True
raise
def expire(self):
""" Return True if the lock is expired or missing; False otherwise. """
if self.isExpired():
self._removeLockDir()
# log("expired lock: %s\n" % self.lockDir)
return True
return False
# Private -------------------------------------------------------
def _makeDir(self):
""" Make sure directory exists """
try:
os.mkdir(self.dir)
# log('created directory: %s\n' % self.dir)
except OSError, err:
if err.errno != errno.EEXIST:
raise
def _removeLockDir(self):
""" Remove lockDir ignoring 'No such file or directory' errors """
try:
os.rmdir(self.lockDir)
except OSError, err:
if err.errno != errno.ENOENT:
raise
class WriteLock(ExclusiveLock):
""" Exclusive Read/Write Lock
When a resource is locked with this lock, clients can't read
or write the resource.
This super-exclusive lock can't be acquired if there are any other
locks, either WriteLock or ReadLocks. When trying to acquire, this
lock will try to expire all existing ReadLocks.
"""
fileName = 'write_lock'
def __init__(self, dir, timeout=None, readlocktimeout=None):
""" Init a write lock
@param dir: the lock directory. Every resource should have one
lock directory, which may contain read or write locks.
@param timeout: while trying to acquire, the lock will expire
other unreleased write locks older than timeout.
@param readlocktimeout: while trying to acquire, the lock will
expire other read locks older than readlocktimeout.
"""
ExclusiveLock.__init__(self, dir, timeout)
if readlocktimeout is None:
self.readlocktimeout = timeout
else:
self.readlocktimeout = readlocktimeout
def acquire(self, timeout=None):
""" Acquire an exclusive write lock
Try to acquire an exclusive lock, then try to expire existing
read locks. If timeout has not passed, the lock is acquired.
Otherwise, the exclusive lock is released and the lock is not
acquired.
Return True if lock acquired, False otherwise.
"""
if self._locked:
raise RuntimeError("lock already locked")
result = False
timer = self.timerClass(timeout)
timer.start()
if ExclusiveLock.acquire(self, timeout):
try:
while timer.haveTime():
self._expireReadLocks()
if not self._haveReadLocks():
result = timer.haveTime()
break
timer.sleep()
finally:
if result:
# log('acquired write lock: %s\n' % (self.lockDir))
return True
else:
self.release()
return False
# Private -------------------------------------------------------
def _expireReadLocks(self):
""" Expire old read locks """
readLockFileName = ReadLock.fileName
for name in os.listdir(self.dir):
if not name.startswith(readLockFileName):
continue
LockDir = os.path.join(self.dir, name)
ExclusiveLock(LockDir, self.readlocktimeout).expire()
def _haveReadLocks(self):
""" Return True if read locks exists; False otherwise """
readLockFileName = ReadLock.fileName
for name in os.listdir(self.dir):
if name.startswith(readLockFileName):
return True
return False
class ReadLock(ExclusiveLock):
""" Read lock
The purpose of this lock is to mark the resource as read only.
Multiple ReadLocks can be acquired for same resource, but no
WriteLock can be acquired until all ReadLocks are released.
Allows only one lock per instance.
"""
fileName = 'read_lock_'
def __init__(self, dir, timeout=None):
""" Init a read lock
@param dir: the lock directory. Every resource should have one
lock directory, which may contain read or write locks.
@param timeout: while trying to acquire, the lock will expire
other unreleased write locks older than timeout.
"""
ExclusiveLock.__init__(self, dir, timeout)
writeLockDir = os.path.join(self.dir, WriteLock.fileName)
self.writeLock = ExclusiveLock(writeLockDir, timeout)
def acquire(self, timeout=None):
""" Try to acquire a 'read' lock
To prevent race conditions, acquire first an exclusive lock,
then acquire a read lock. Finally release the exclusive lock so
other can have read lock, too.
"""
if self._locked:
raise RuntimeError("lock already locked")
if self.writeLock.acquire(timeout):
try:
self.lockDir = tempfile.mkdtemp('', self.fileName, self.dir)
self._locked = True
# log('acquired read lock: %s\n' % self.lockDir)
return True
finally:
self.writeLock.release()
return False
class LazyReadLock(ReadLock):
""" Lazy Read lock
See ReadLock, but we do an optimization here:
If (and ONLY if) the resource protected by this lock is updated in a POSIX
style "write new content to tmpfile, rename tmpfile -> origfile", then reading
from an open origfile handle will give either the old content (when opened
before the rename happens) or the new content (when opened after the rename
happened), but never cause any trouble. This means that we don't have to lock
at all in that case.
Of course this doesn't work for us on the win32 platform:
* using MoveFileEx requires opening the file with some FILE_SHARE_DELETE
mode - we currently don't do that
* Win 95/98/ME do not have MoveFileEx
We currently solve by using the non-lazy locking code in ReadLock class.
"""
def __init__(self, dir, timeout=None):
if sys.platform == 'win32':
ReadLock.__init__(self, dir, timeout)
else: # POSIX
self._locked = False
def acquire(self, timeout=None):
if sys.platform == 'win32':
return ReadLock.acquire(self, timeout)
else: # POSIX
self._locked = True
return True
def release(self):
if sys.platform == 'win32':
return ReadLock.release(self)
else: # POSIX
self._locked = False
def exists(self):
if sys.platform == 'win32':
return ReadLock.exists(self)
else: # POSIX
return True
def isExpired(self):
if sys.platform == 'win32':
return ReadLock.isExpired(self)
else: # POSIX
return True
def expire(self):
if sys.platform == 'win32':
return ReadLock.expire(self)
else: # POSIX
return True
class LazyWriteLock(WriteLock):
""" Lazy Write lock
See WriteLock and LazyReadLock docs.
"""
def __init__(self, dir, timeout=None):
if sys.platform == 'win32':
WriteLock.__init__(self, dir, timeout)
else: # POSIX
self._locked = False
def acquire(self, timeout=None):
if sys.platform == 'win32':
return WriteLock.acquire(self, timeout)
else: # POSIX
self._locked = True
return True
def release(self):
if sys.platform == 'win32':
return WriteLock.release(self)
else: # POSIX
self._locked = False
def exists(self):
if sys.platform == 'win32':
return WriteLock.exists(self)
else: # POSIX
return True
def isExpired(self):
if sys.platform == 'win32':
return WriteLock.isExpired(self)
else: # POSIX
return True
def expire(self):
if sys.platform == 'win32':
return WriteLock.expire(self)
else: # POSIX
return True
|