UpgradableLock: release exclusive lock in case of exceptions

also: add some comments about how to use the locks in the safest way
This commit is contained in:
Thomas Waldmann 2015-11-21 16:53:33 +01:00
parent f19e95fcf7
commit 1093894be8
1 changed files with 26 additions and 7 deletions

View File

@ -99,7 +99,14 @@ class NotMyLock(LockErrorT):
class ExclusiveLock: class ExclusiveLock:
"""An exclusive Lock based on mkdir fs operation being atomic""" """An exclusive Lock based on mkdir fs operation being atomic.
If possible, try to use the contextmanager here like:
with ExclusiveLock(...) as lock:
...
This makes sure the lock is released again if the block is left, no
matter how (e.g. if an exception occurred).
"""
def __init__(self, path, timeout=None, sleep=None, id=None): def __init__(self, path, timeout=None, sleep=None, id=None):
self.timeout = timeout self.timeout = timeout
self.sleep = sleep self.sleep = sleep
@ -220,6 +227,12 @@ class UpgradableLock:
Typically, write access to a resource needs an exclusive lock (1 writer, Typically, write access to a resource needs an exclusive lock (1 writer,
noone is allowed reading) and read access to a resource needs a shared noone is allowed reading) and read access to a resource needs a shared
lock (multiple readers are allowed). lock (multiple readers are allowed).
If possible, try to use the contextmanager here like:
with UpgradableLock(...) as lock:
...
This makes sure the lock is released again if the block is left, no
matter how (e.g. if an exception occurred).
""" """
def __init__(self, path, exclusive=False, sleep=None, timeout=None, id=None): def __init__(self, path, exclusive=False, sleep=None, timeout=None, id=None):
self.path = path self.path = path
@ -262,12 +275,18 @@ class UpgradableLock:
timer = TimeoutTimer(self.timeout, sleep).start() timer = TimeoutTimer(self.timeout, sleep).start()
while True: while True:
self._lock.acquire() self._lock.acquire()
if remove is not None: try:
self._roster.modify(remove, REMOVE) if remove is not None:
remove = None self._roster.modify(remove, REMOVE)
if len(self._roster.get(SHARED)) == 0: remove = None
return # we are the only one and we keep the lock! if len(self._roster.get(SHARED)) == 0:
self._lock.release() return # we are the only one and we keep the lock!
except:
# avoid orphan lock when an exception happens here, e.g. Ctrl-C!
self._lock.release()
raise
else:
self._lock.release()
if timer.timed_out_or_sleep(): if timer.timed_out_or_sleep():
raise LockTimeout(self.path) raise LockTimeout(self.path)