/usr/share/pyshared/MoinMoin/util/lock.py is in python-moinmoin 1.9.3-1ubuntu2.3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 398 399 400 401 402 | # -*- coding: iso-8859-1 -*-
"""
MoinMoin - locking functions
@copyright: 2005 Florian Festi, Nir Soffer,
2008 MoinMoin:ThomasWaldmann
@license: GNU GPL, see COPYING for details.
"""
import os, sys, tempfile, time, errno
from MoinMoin import log
logging = log.getLogger(__name__)
from MoinMoin.util import filesys
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:
filesys.mkdir(self.lockDir)
self._locked = True
logging.debug('acquired exclusive lock: %s' % (self.lockDir, ))
return True
except OSError, err:
if err.errno != errno.EEXIST:
raise
if self.expire():
continue # Try immediately to acquire
timer.sleep()
logging.debug('failed to acquire exclusive lock: %s' % (self.lockDir, ))
return False
def release(self):
""" Release the lock """
if not self._locked:
raise RuntimeError('lock already released: %s' % self.lockDir)
self._removeLockDir()
self._locked = False
logging.debug('released lock: %s' % 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() - filesys.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()
logging.debug("expired lock: %s" % self.lockDir)
return True
return False
# Private -------------------------------------------------------
def _makeDir(self):
""" Make sure directory exists """
try:
filesys.mkdir(self.dir)
logging.debug('created directory: %s' % self.dir)
except OSError, err:
if err.errno != errno.EEXIST:
raise
def _removeLockDir(self):
""" Remove lockDir ignoring 'No such file or directory' errors """
try:
filesys.rmdir(self.lockDir)
logging.debug('removed directory: %s' % self.dir)
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:
logging.debug('acquired write lock: %s' % 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
logging.debug('acquired read lock: %s' % 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
|