From e8242bd13de1f158cca48bb4f4a6f6bf56857f4a Mon Sep 17 00:00:00 2001 From: Ofer Koren Date: Mon, 22 Jun 2020 09:09:01 +0300 Subject: [PATCH] sync: allow specifying identity of reader when using RWLock --- easypy/sync.py | 34 ++++++++++++++++++++++++++++------ tests/test_rwlock.py | 28 +++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/easypy/sync.py b/easypy/sync.py index 7ce85eaf..dc7bb129 100644 --- a/easypy/sync.py +++ b/easypy/sync.py @@ -684,7 +684,7 @@ def __init__(self, name=None): self._lease_timer = None def __repr__(self): - owners = ", ".join(map(str, sorted(self.owners.keys()))) + owners = ", ".join(sorted(map(str, self.owners.keys()))) lease_timer = self._lease_timer # touch once to avoid races if lease_timer: mode = "exclusively ({})".format(lease_timer.elapsed) @@ -700,25 +700,47 @@ def __call__(self): return self def __enter__(self): + self.acquire() + return self + + def acquire(self, identifier=None): + """ + Acquire the lock as a reader. + Optionally specify the identity of the reader (defaults to thready identity). + """ while not self.cond.acquire(timeout=15): _logger.debug("%s - waiting...", self) + if not identifier: + identifier = _get_my_ident() + try: - self.owners[_get_my_ident()] += 1 + self.owners[identifier] += 1 _verbose_logger.debug("%s - acquired (non-exclusively)", self) return self finally: self.cond.release() def __exit__(self, *args): + self.release() + + def release(self, identifier=None): + """ + Release the lock as a reader. + Optionally specify the identity of the reader (defaults to thready identity). + """ + while not self.cond.acquire(timeout=15): _logger.debug("%s - waiting...", self) + if not identifier: + identifier = _get_my_ident() try: - my_ident = _get_my_ident() - self.owners[my_ident] -= 1 - if not self.owners[my_ident]: - self.owners.pop(my_ident) # don't inflate the soft lock keys with threads that does not own it + if not self.owners[identifier]: + raise RuntimeError("cannot release un-acquired lock") + self.owners[identifier] -= 1 + if not self.owners[identifier]: + self.owners.pop(identifier) self.cond.notify() _verbose_logger.debug("%s - released (non-exclusive)", self) finally: diff --git a/tests/test_rwlock.py b/tests/test_rwlock.py index 4ab12102..e7e0c069 100644 --- a/tests/test_rwlock.py +++ b/tests/test_rwlock.py @@ -1,5 +1,5 @@ import threading - +import pytest import logging from easypy.concurrency import concurrent from easypy.sync import RWLock @@ -76,3 +76,29 @@ def write(): main_ctrl.clear() logging.info("write lock released") assert not state.reading and not state.writing + + +def test_rwlock_different_threads(): + lock = RWLock("test") + e = threading.Event() + + def a(id=None): + lock.acquire(id) + e.wait() + + def b(id=None): + lock.release(id) + e.set() + + with concurrent(a): + assert lock.owners + with pytest.raises(RuntimeError): + b() + e.set() + assert lock.owners + + with concurrent(a, "same"): + assert lock.owners + with concurrent(b, "same"): + pass + assert lock.owners