From fe3783ae6e730d00d37e0e25259a2d724477d932 Mon Sep 17 00:00:00 2001 From: David Lehman Date: Mon, 11 Aug 2014 14:13:26 -0500 Subject: [PATCH 12/26] Add several more conveniences for managing locks. New functions lock_objects and unlock_objects provide a way to pass a variable-length list of objects with lock attributes to acquire. lock_objects(x, y) Acquires x.lock and then y.lock. unlock_objects(x, y) Releases y.lock and then x.lock. New context manager LockList provides a contextmanager class using lock_object and unlock_objects. with LockList(dev1, fmt1, x): ... Acquires dev1.lock, fmt1.lock, and x.lock, holding them while executing the enclosed block, then releases them LIFO. New decorator lock_args acquires the lock of function arguments specified by name. @lock_args("foo") def func(x, foo, bar=None): ... Acquires foo.lock before running func. New decorators lock_attr and lock_list_attr both acquire locks on attributes of an object whose method they will run afterward. class StorageDevice(object): ... @lock_attr("format") def minSize(self): ... Acquires self.format.lock before running the minSize method. class StorageDevice(object): ... @lock_list_attr("parents") def setup(self): ... Acquires [p.lock for p in self.parents] before running setup. --- blivet/threads.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/blivet/threads.py b/blivet/threads.py index 5d9430e..2ba6280 100644 --- a/blivet/threads.py +++ b/blivet/threads.py @@ -55,6 +55,109 @@ class LockedObjectID(ObjectID): self.lock = RLockID(self.id) # pylint: disable=attribute-defined-outside-init return self +def lock_attr(attr): + """ Run method with object in object attribute locked. + + For example, given a blivet.devices.StorageDevice instance, the + following decorator will acquire the lock on the instance's format + attribute before running the decorated method:: + + @lock_attr("format") + def setup(self): + ... + + """ + def wrap(m): + @wraps(m) + def wrapper(*args, **kwargs): + self = args[0] + with getattr(self, attr): + return m(*args, **kwargs) + + return wrapper + + return wrap + +def lock_list_attr(attr): + """ Run method with objects in list object attribute locked. + + For example, given a blivet.devices.StorageDevice instance, the + following decorator will acquire locks on every device in that + instance's self.parents list before running the decorated method:: + + @lock_list_attr("parents") + def setup(self): + ... + + """ + def wrap(m): + @wraps(m) + def wrapper(*args, **kwargs): + self = args[0] + with LockList(getattr(self, attr, [])): + return m(*args, **kwargs) + + return wrapper + + return wrap + +def lock_objects(objects): + """ Acquire the mutex for every object in a list. + + All objects must have a lock attribute with acquire and release methods. + + XXX If/when we move to strictly python 3.1 or later we can implement + this using ExitStack. + """ + for obj in objects: + log.debug(" acquiring %s", obj.lock) + obj.lock.acquire() + +def unlock_objects(objects): + """ release locks on listed objects in reverse order """ + for obj in reversed(objects): + log.debug(" releasing %s", obj.lock) + obj.lock.release() + +class LockList(object): + """ Context manager which acquires locks on a list of objects. """ + def __init__(self, objects): + self.objects = objects or [] + + def __enter__(self): + lock_objects(self.objects) + + def __exit__(self, e_type, e_value, e_tb): + unlock_objects(self.objects) + +def lock_args(*arg_names): + """ Aqcuire lock attr of named args before running a callable. """ + def with_lock(f): + @wraps(f) + def run_with_lock(*args, **kwargs): + objs = [] + for name in arg_names: + if name in kwargs: + arg = kwargs[name] + else: + if name in f.func_code.co_varnames: + arg_idx = f.func_code.co_varnames.index(name) + arg = args[arg_idx] + + if arg: + objs.append(arg) + else: + log.debug("couldn't find lockable arg '%s'", name) + + log.debug("thread %s acquiring locks %s before calling %s", + currentThread().name, arg_names, f.func_code.co_name) + with LockList(objs): + return f(*args, **kwargs) + + return run_with_lock + + return with_lock + def exclusive(m): """ Run a bound method after aqcuiring the instance's lock. """ @wraps(m) -- 1.9.3