Always use readers-writer lock in storage locking

This commit is contained in:
Unrud 2016-05-22 09:16:18 +02:00
parent bca6cec6b3
commit eb9218354c

View File

@ -517,20 +517,31 @@ class Collection(BaseCollection):
return "".join([item.serialize() for item in items]) return "".join([item.serialize() for item in items])
return "" return ""
_lock = threading.Lock() _lock = threading.Condition()
_readers = 0
_writer = False
@classmethod @classmethod
@contextmanager @contextmanager
def acquire_lock(cls, mode): def acquire_lock(cls, mode):
class Lock: def condition():
def __init__(self, release_method): if mode == "r":
self._release_method = release_method return not cls._writer
else:
def release(self): return not cls._writer and cls._readers == 0
self._release_method()
if mode not in ("r", "w"): if mode not in ("r", "w"):
raise ValueError("Invalid lock mode: %s" % mode) raise ValueError("Invalid lock mode: %s" % mode)
# Use a primitive lock which only works within one process as a
# precondition for inter-process file-based locking
with cls._lock:
cls._lock.wait_for(condition)
if mode == "r":
cls._readers += 1
# notify additional potential readers
cls._lock.notify()
else:
cls._writer = True
folder = os.path.expanduser( folder = os.path.expanduser(
cls.configuration.get("storage", "filesystem_folder")) cls.configuration.get("storage", "filesystem_folder"))
if not os.path.exists(folder): if not os.path.exists(folder):
@ -543,13 +554,12 @@ class Collection(BaseCollection):
os.chmod(lock_path, stat.S_IWUSR | stat.S_IRUSR) os.chmod(lock_path, stat.S_IWUSR | stat.S_IRUSR)
except OSError: except OSError:
cls.logger.debug("Failed to set permissions on lock file") cls.logger.debug("Failed to set permissions on lock file")
locked = False
if os.name == "nt": if os.name == "nt":
handle = msvcrt.get_osfhandle(lock_file.fileno()) handle = msvcrt.get_osfhandle(lock_file.fileno())
flags = LOCKFILE_EXCLUSIVE_LOCK if mode == "w" else 0 flags = LOCKFILE_EXCLUSIVE_LOCK if mode == "w" else 0
overlapped = Overlapped() overlapped = Overlapped()
if lock_file_ex(handle, flags, 0, 1, 0, overlapped): if not lock_file_ex(handle, flags, 0, 1, 0, overlapped):
locked = True cls.logger.debug("Locking not supported")
elif os.name == "posix": elif os.name == "posix":
operation = fcntl.LOCK_EX if mode == "w" else fcntl.LOCK_SH operation = fcntl.LOCK_EX if mode == "w" else fcntl.LOCK_SH
# According to documentation flock() is emulated with fcntl() on # According to documentation flock() is emulated with fcntl() on
@ -561,18 +571,11 @@ class Collection(BaseCollection):
try: try:
fcntl.flock(lock_file.fileno(), operation) fcntl.flock(lock_file.fileno(), operation)
except OSError: except OSError:
pass cls.logger.debug("Locking not supported")
else:
locked = True
if locked:
lock = Lock(lock_file.close)
else:
cls.logger.debug("Locking not supported")
lock_file.close()
# Fallback to primitive lock which only works within one process
# and doesn't distinguish between shared and exclusive access.
# TODO: use readerswriter lock
cls._lock.acquire()
lock = Lock(cls._lock.release)
yield yield
lock.release() with cls._lock:
if mode == "r":
cls._readers -= 1
else:
cls._writer = False
cls._lock.notify()