Add a locked decorator

This decorator is useful to lock a instance method
where that instance object that the method is attached
to has a lock object connected to an attribute (or list of
locks) that should be acquired before/after running the
decorated method.
This commit is contained in:
Joshua Harlow
2015-06-03 12:51:12 -07:00
parent c3ea6a8ec3
commit 1f30ce0b61
4 changed files with 75 additions and 0 deletions

View File

@@ -1,4 +1,8 @@
0.7:
- Add helpful `locked` decorator that can
lock a method using a found attribute (a lock
object or list of lock objects) in the
instance the method is attached to.
0.6:
- Allow the sleep function to be provided (so that
various alternatives other than time.sleep can

View File

@@ -20,6 +20,7 @@
from __future__ import absolute_import
from fasteners.lock import locked # noqa
from fasteners.lock import read_locked # noqa
from fasteners.lock import write_locked # noqa

View File

@@ -17,12 +17,19 @@
# 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
import six
from fasteners import _utils
try:
# Used for the reader-writer lock get the right
# thread 'hack' (needed below).
@@ -228,3 +235,63 @@ class ReaderWriterLock(object):
with self._cond:
self._writer = None
self._cond.notify_all()
@contextlib.contextmanager
def try_lock(lock):
"""Attempts to acquire a lock, and auto releases if acquired (on exit)."""
# NOTE(harlowja): the keyword argument for 'blocking' does not work
# in py2.x and only is fixed in py3.x (this adjustment is documented
# and/or debated in http://bugs.python.org/issue10789); so we'll just
# stick to the format that works in both (oddly the keyword argument
# works in py2.x but only with reentrant locks).
was_locked = lock.acquire(False)
try:
yield was_locked
finally:
if was_locked:
lock.release()
def locked(*args, **kwargs):
"""A locking **method** decorator.
It will look for a provided attribute (typically a lock or a list
of locks) on the first argument of the function decorated (typically this
is the 'self' object) and before executing the decorated function it
activates the given lock or list of locks as a context manager,
automatically releasing that lock on exit.
NOTE(harlowja): if no attribute name is provided then by default the
attribute named '_lock' is looked for (this attribute is expected to be
the lock/list of locks object/s) in the instance object this decorator
is attached to.
"""
def decorator(f):
attr_name = kwargs.get('lock', '_lock')
@six.wraps(f)
def wrapper(self, *args, **kwargs):
attr_value = getattr(self, attr_name)
if isinstance(attr_value, (tuple, list)):
with ExitStack() as stack:
for lock in attr_value:
stack.enter_context(lock)
return f(self, *args, **kwargs)
else:
lock = attr_value
with lock:
return f(self, *args, **kwargs)
return wrapper
# This is needed to handle when the decorator has args or the decorator
# doesn't have args, python is rather weird here...
if kwargs or not args:
return decorator
else:
if len(args) == 1:
return decorator(args[0])
else:
return decorator

View File

@@ -26,6 +26,9 @@ 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(