From 3dacbff42c98998330461b02ef2726949e72697c Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Fri, 8 Aug 2014 07:58:42 -0500
Subject: [PATCH 08/26] Make the Device class hierarchy thread-safe.

Add a reentrant lock to Device. Each Device has an RLock with an id
attribute matching the device's. Use a metaclass which wraps every method
call in a decorator which grabs the instance's lock before calling the
actual method.
---
 blivet/devices/container.py |  3 ++-
 blivet/devices/device.py    |  7 +++++--
 blivet/devices/lvm.py       |  3 ++-
 blivet/devices/raid.py      |  5 ++---
 blivet/threads.py           | 29 +++++++++++++++++++++++++++--
 5 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/blivet/devices/container.py b/blivet/devices/container.py
index 4604231..53da301 100644
--- a/blivet/devices/container.py
+++ b/blivet/devices/container.py
@@ -26,13 +26,14 @@ from six import add_metaclass
 from .. import errors
 from ..storage_log import log_method_call
 from ..formats import get_device_format_class
+from ..threads import SynchronizedABCMeta
 
 import logging
 log = logging.getLogger("blivet")
 
 from .storage import StorageDevice
 
-@add_metaclass(abc.ABCMeta)
+@add_metaclass(SynchronizedABCMeta)
 class ContainerDevice(StorageDevice):
     """ A device that aggregates a set of member devices.
 
diff --git a/blivet/devices/device.py b/blivet/devices/device.py
index 3931eda..a243a22 100644
--- a/blivet/devices/device.py
+++ b/blivet/devices/device.py
@@ -24,13 +24,16 @@ import pprint
 
 from .. import util
 from ..storage_log import log_method_call
+from six import add_metaclass
+from ..threads import LockedObjectID, SynchronizedMeta
 
 import logging
 log = logging.getLogger("blivet")
 
 from .lib import ParentList
 
-class Device(util.ObjectID):
+@add_metaclass(SynchronizedMeta)
+class Device(LockedObjectID):
     """ A generic device.
 
         Device instances know which devices they depend upon (parents
@@ -75,7 +78,7 @@ class Device(util.ObjectID):
             :keyword parents: a list of parent devices
             :type parents: list of :class:`Device` instances
         """
-        util.ObjectID.__init__(self)
+        super(Device, self).__init__()
         self.kids = 0
 
         # Copy only the validity check from _setName so we don't try to check a
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py
index 1f00dae..a091fef 100644
--- a/blivet/devices/lvm.py
+++ b/blivet/devices/lvm.py
@@ -35,6 +35,7 @@ from ..formats import getFormat
 from ..storage_log import log_method_call
 from .. import udev
 from ..size import Size
+from ..threads import SynchronizedMeta
 
 import logging
 log = logging.getLogger("blivet")
@@ -789,7 +790,7 @@ class LVMLogicalVolumeDevice(DMDevice):
 
         return True
 
-@add_metaclass(abc.ABCMeta)
+@add_metaclass(SynchronizedMeta)
 class LVMSnapShotBase(object):
     """ Abstract base class for lvm snapshots
 
diff --git a/blivet/devices/raid.py b/blivet/devices/raid.py
index 60fb5a2..8638ecf 100644
--- a/blivet/devices/raid.py
+++ b/blivet/devices/raid.py
@@ -19,16 +19,15 @@
 # Red Hat Author(s): Anne Mulhern <amulhern@redhat.com>
 #
 
-import abc
-
 from six import add_metaclass
 
 from .. import errors
 from ..i18n import _, P_
+from ..threads import SynchronizedABCMeta
 
 from .storage import StorageDevice
 
-@add_metaclass(abc.ABCMeta)
+@add_metaclass(SynchronizedABCMeta)
 class RaidDevice(StorageDevice):
     """ Metaclass for devices that support RAID in some form. """
 
diff --git a/blivet/threads.py b/blivet/threads.py
index 2073431..5d9430e 100644
--- a/blivet/threads.py
+++ b/blivet/threads.py
@@ -23,6 +23,7 @@
 from threading import _RLock, currentThread
 from functools import wraps
 from types import FunctionType
+from abc import ABCMeta
 
 from .util import ObjectID
 
@@ -62,9 +63,33 @@ def exclusive(m):
         # instance we have a function object -- not a method. As a result, we
         # cannot use m.__self__ and instead must rely on the fact that the
         # instance is always passed as the first argument to the method.
+        lock = args[0].lock
         log.debug("thread %s acquiring lock %s before calling %s",
-                  currentThread().name, args[0].lock, m.func_code.co_name)
-        with args[0].lock:
+                  currentThread().name, lock, m.func_code.co_name)
+        with lock:
             return m(*args, **kwargs)
 
     return run_with_lock
+
+class SynchronizedMeta(type):
+    """ Metaclass that wraps all methods with the exclusive decorator.
+
+        To prevent specific methods from being wrapped, add the method name(s)
+        to a class attribute called _unsynchronized_methods (list of str).
+    """
+    def __new__(cls, name, bases, dct):
+        wrapped = {}
+        blacklist = dct.get('_unsynchronized_methods', [])
+        for n in dct:
+            obj = dct[n]
+            # Do not decorate class or static methods.
+            if type(dct[n]) is FunctionType:
+                if n not in blacklist:
+                    obj = exclusive(obj)
+
+            wrapped[n] = obj
+
+        return super(SynchronizedMeta, cls).__new__(cls, name, bases, wrapped)
+
+class SynchronizedABCMeta(SynchronizedMeta, ABCMeta):
+    pass
-- 
1.9.3

