diff --git a/src/labthings/sync/lock.py b/src/labthings/sync/lock.py index 5a5aeafc..c3a65b52 100644 --- a/src/labthings/sync/lock.py +++ b/src/labthings/sync/lock.py @@ -53,9 +53,11 @@ def _owner(self): @contextmanager def __call__(self, timeout=sentinel, blocking: bool = True): result = self.acquire(timeout=timeout, blocking=blocking) - yield result - if result: - self.release() + try: + yield result + finally: + if result: + self.release() def locked(self): """ """ @@ -122,9 +124,11 @@ def _owner(self): @contextmanager def __call__(self, timeout=sentinel, blocking: bool = True): result = self.acquire(timeout=timeout, blocking=blocking) - yield result - if result: - self.release() + try: + yield result + finally: + if result: + self.release() def acquire(self, blocking: bool = True, timeout=sentinel): """ diff --git a/tests/test_sync_lock.py b/tests/test_sync_lock.py index 0f085f0e..53541b71 100644 --- a/tests/test_sync_lock.py +++ b/tests/test_sync_lock.py @@ -108,3 +108,33 @@ def g(): with pytest.raises(lock.LockError): with this_lock(timeout=0.01): pass + +class DummyException(Exception): + pass + +def test_rlock_released_after_error_args(this_lock): + """If an exception occurs in a with block, the lock should release. + + NB there are two sets of code that do this - one if arguments are + given (i.e. the __call__ method of the lock class) and one without + arguments (i.e. the __enter__ and __exit__ methods). + + See the following function for the no-arguments version. + """ + try: + with this_lock(): + assert this_lock.locked() + raise DummyException() + except DummyException: + pass + assert not this_lock.locked() + +def test_rlock_released_after_error_noargs(this_lock): + """If an exception occurs in a with block, the lock should release.""" + try: + with this_lock: + assert this_lock.locked() + raise DummyException() + except DummyException: + pass + assert not this_lock.locked() \ No newline at end of file