diff --git a/fasteners/_utils.py b/fasteners/_utils.py index 4ac80a4..f5b87e8 100644 --- a/fasteners/_utils.py +++ b/fasteners/_utils.py @@ -23,6 +23,30 @@ except ImportError: from time import time as now +class LockStack(object): + """Simple lock stack to get and release many locks.""" + + def __init__(self): + self._stack = [] + + def acquire_lock(self, lock): + gotten = lock.acquire() + self._stack.append(lock) + return gotten + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + while self._stack: + lock = self._stack.pop() + try: + lock.release() + except Exception: + # Always suppress this exception... + pass + + class StopWatch(object): """A really basic stop watch.""" diff --git a/fasteners/lock.py b/fasteners/lock.py index a72a6fa..af7281f 100644 --- a/fasteners/lock.py +++ b/fasteners/lock.py @@ -17,15 +17,12 @@ # License for the specific language governing permissions and limitations # under the License. -try: - from contextlib import ExitStack # noqa -except ImportError: - from contextlib2 import ExitStack # noqa - import collections import contextlib import threading +from fasteners import _utils + import six try: @@ -273,9 +270,9 @@ def locked(*args, **kwargs): def wrapper(self, *args, **kwargs): attr_value = getattr(self, attr_name) if isinstance(attr_value, (tuple, list)): - with ExitStack() as stack: + with _utils.LockStack() as stack: for lock in attr_value: - stack.enter_context(lock) + stack.acquire_lock(lock) return f(self, *args, **kwargs) else: lock = attr_value diff --git a/fasteners/tests/test_decorators.py b/fasteners/tests/test_decorators.py index d184516..ce6daa1 100644 --- a/fasteners/tests/test_decorators.py +++ b/fasteners/tests/test_decorators.py @@ -32,6 +32,22 @@ class Locked(object): cb(self._lock.locked()) +class ManyLocks(object): + def __init__(self, amount): + self._lock = [] + for _i in range(0, amount): + self._lock.append(threading.Lock()) + + @fasteners.locked + def i_am_locked(self, cb): + gotten = [lock.locked() for lock in self._lock] + cb(gotten) + + def i_am_not_locked(self, cb): + gotten = [lock.locked() for lock in self._lock] + cb(gotten) + + class RWLocked(object): def __init__(self): self._lock = fasteners.ReaderWriterLock() @@ -54,6 +70,11 @@ class DecoratorsTest(test.TestCase): obj.i_am_locked(lambda is_locked: self.assertTrue(is_locked)) obj.i_am_not_locked(lambda is_locked: self.assertFalse(is_locked)) + def test_many_locked(self): + obj = ManyLocks(10) + obj.i_am_locked(lambda gotten: self.assertTrue(all(gotten))) + obj.i_am_not_locked(lambda gotten: self.assertEqual(0, sum(gotten))) + def test_read_write_locked(self): reader = fasteners.ReaderWriterLock.READER writer = fasteners.ReaderWriterLock.WRITER diff --git a/setup.py b/setup.py index 03b212b..9dc0030 100644 --- a/setup.py +++ b/setup.py @@ -26,9 +26,6 @@ with open("README.rst", "r") as readme: install_requires = [ 'six', - # Only needed for <= python 3.3, replace me with requirement - # markers in the future... - 'contextlib2', ] setup(