Nothing fancy here. Locks can take many forms. Here are a few.
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 | """lock module"""
from time import time, sleep
import threading
import fcntl
from abc import ABCMeta
def wait(delay):
delay = min(delay*2, .05)
sleep(delay)
return delay
class Lock(object):
"""A generic lock object, based on threading.Lock()."""
__metaclass__ = ABCMeta
def __init__(self):
self._locked = False
def acquire(self, blocking=True, timeout=None):
if blocking:
delay = 0.0005
if timeout is None:
while self._locked:
delay = wait(delay)
else:
end = time() + timeout
while time() < end:
if not self._locked:
break
delay = wait(delay)
else:
return False
self._locked = True
return True
elif timeout is not None:
raise ValueError("can't specify a timeout "
"for a non-blocking call")
if self._locked:
return False
self._locked = True
return True
def release(self):
if not self._locked:
raise RuntimeError("release unlocked lock")
self._locked = False
def locked(self):
return self._locked
# for the "with" statement
def __enter__(self): self.acquire()
def __exit__(self, *args): self.release()
Lock.register(type(threading.Lock()))
class ThreadingLock(Lock):
"""A subclass of Lock that wraps a threading lock."""
def __init__(self):
self._lock = threading.Lock()
def acquire(self, blocking=True, timeout=None):
if not blocking and timeout is not None:
raise ValueError("can't specify a timeout "
"for a non-blocking call")
if timeout is None:
return self._lock.acquire(blocking)
else:
#return self._lock.acquire(blocking, timeout)
raise NotImplementedError
def release(self):
self._lock.release()
def locked(self):
return self._lock.locked()
class LockableFile(object):
"""A POSIX file with filesystem locking."""
def __init__(self, file, mode="r"):
self._own = False
if isinstance(file, str):
file = open(file, mode)
self._own = True
self.file = file
self._locked = False
def __getattr__(self, name):
return getattr(self.file, name)
def lock(self, timeout=3):
if self._locked:
return
# couldn't get lock
error = None
end = time() + timeout
while time() < end:
try:
fcntl.lockf(self.file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError, e:
error = e
delay = wait(delay)
else:
break
else:
if error: raise error
self._locked = True
def unlock(self):
fcntl.lockf(self.file, fcntl.LOCK_UN)
self._locked = False
def __enter__(self):
self.lock()
def __exit__(self, *args):
self.unlock()
if self._own:
self.file.close()
class LockFile(Lock):
"""A Lock subclass implemented on top of a lock file."""
DEFAULT = "locked"
def __init__(self, path, value=DEFAULT):
self.path = path
self.default = value
def acquire(self, value=None, blocking=True, timeout=None):
if value is None:
value = self.default
if value is None:
raise TypeError("missing value")
try:
lockfile = LockableFile(self.path, "r+")
except IOError:
lockfile = LockableFile(self.path, "a+")
lockfile.seek(0)
with lockfile:
content = lockfile.read()
#if value is not self.DEFAULT and content == value:
# return True
if not content:
lockfile.seek(0)
lockfile.truncate()
lockfile.write(value)
return True
if blocking:
raise NotImplementedError
elif timeout is not None:
raise ValueError("can't specify a timeout "
"for a non-blocking call")
return False
def release(self, value=None):
if value is None:
value = self.default
value = str(value)
try:
lockfile = LockableFile(self.path, "r+")
except IOError:
return
with lockfile:
content = lockfile.read()
if content != value:
raise RuntimeError("got '%s', expected '%s'" %
(value, content))
lockfile.seek(0)
lockfile.truncate()
def value(self):
return open(self.path).read()
locked = value
def clear(self):
try:
lockfile = LockableFile(self.path, "r+")
except IOError:
return
lockfile.truncate()
|
Examples
lock = Lock()
assert lock.acquire()
assert not lock.acquire(False)
assert not lock.acquire(timeout=0.5)
assert lock.locked()
lock.release()
assert not lock.locked()
lock = ThreadingLock()
assert lock.acquire(False)
assert not lock.acquire(False)
assert lock.locked()
lock.release()
assert not lock.locked()
lock = LockFile("/tmp/.test_lock_1")
lock.clear()
assert lock.acquire(blocking=False)
assert not lock.acquire(blocking=False)
assert lock.locked()
lock.release()
assert not lock.locked()
lock = LockFile("/tmp/.test_lock_2")
lock.clear()
assert lock.acquire("disabled", blocking=False)
assert not lock.acquire("disabled", blocking=False)
assert lock.locked()
assert not lock.acquire("12345", blocking=False)
try: lock.release("12345")
except RuntimeError: pass
else: assert False, "expected RuntimeError"
lock.release("disabled")
assert not lock.locked()
assert lock.acquire("12345", blocking=False)
assert lock.locked()
assert not lock.acquire("12345", blocking=False)
assert not lock.acquire("disabled", blocking=False)
lock.release("12345")
assert not lock.locked()
lock = LockFile("/tmp/.test_lock_3")
lock.clear()
assert lock.acquire(blocking=False)
assert lock.locked()
lock = LockFile("/tmp/.test_lock_3")
assert not lock.acquire(blocking=False)
lock.release()
assert not lock.locked()
lock = LockFile("/tmp/.test_lock_3")
assert lock.acquire(blocking=False)
assert lock.locked()
lock.release()