Locking: add ssynchronized decorator
authorGuido Trotter <ultrotter@google.com>
Tue, 8 Jul 2008 08:40:42 +0000 (08:40 +0000)
committerGuido Trotter <ultrotter@google.com>
Tue, 8 Jul 2008 08:40:42 +0000 (08:40 +0000)
This patch creates a new decorator function ssynchronized in the locking
library, which takes as input a SharedLock, and synchronizes access to
the decorated functions using it. The usual SharedLock semantics apply,
so it's possible to call more than one synchronized function at the same
time, when the lock is acquired in shared mode, and still protect
against exclusive access.

The patch also adds a few unit test to check the basic decorator's
functionality, and to provide an example on how to use it.

Reviewed-by: iustinp

lib/locking.py
test/ganeti.locking_unittest.py

index c230be8..7be1551 100644 (file)
@@ -29,6 +29,25 @@ from ganeti import errors
 from ganeti import utils
 
 
+def ssynchronized(lock, shared=0):
+  """Shared Synchronization decorator.
+
+  Calls the function holding the given lock, either in exclusive or shared
+  mode. It requires the passed lock to be a SharedLock (or support its
+  semantics).
+
+  """
+  def wrap(fn):
+    def sync_function(*args, **kwargs):
+      lock.acquire(shared=shared)
+      try:
+        return fn(*args, **kwargs)
+      finally:
+        lock.release()
+    return sync_function
+  return wrap
+
+
 class SharedLock:
   """Implements a shared lock.
 
index e254e85..41ec354 100755 (executable)
@@ -32,6 +32,11 @@ from ganeti import errors
 from threading import Thread
 
 
+# This is used to test the ssynchronize decorator.
+# Since it's passed as input to a decorator it must be declared as a global.
+_decoratorlock = locking.SharedLock()
+
+
 class TestSharedLock(unittest.TestCase):
   """SharedLock tests"""
 
@@ -230,6 +235,60 @@ class TestSharedLock(unittest.TestCase):
     self.assertEqual(self.done.get(True, 1), 'ERR')
 
 
+class TestSSynchronizedDecorator(unittest.TestCase):
+  """Shared Lock Synchronized decorator test"""
+
+  def setUp(self):
+    # helper threads use the 'done' queue to tell the master they finished.
+    self.done = Queue.Queue(0)
+
+  @locking.ssynchronized(_decoratorlock)
+  def _doItExclusive(self):
+    self.assert_(_decoratorlock._is_owned())
+    self.done.put('EXC')
+
+  @locking.ssynchronized(_decoratorlock, shared=1)
+  def _doItSharer(self):
+    self.assert_(_decoratorlock._is_owned(shared=1))
+    self.done.put('SHR')
+
+  def testDecoratedFunctions(self):
+    self._doItExclusive()
+    self.assert_(not _decoratorlock._is_owned())
+    self._doItSharer()
+    self.assert_(not _decoratorlock._is_owned())
+
+  def testSharersCanCoexist(self):
+    _decoratorlock.acquire(shared=1)
+    Thread(target=self._doItSharer).start()
+    self.assert_(self.done.get(True, 1))
+    _decoratorlock.release()
+
+  def testExclusiveBlocksExclusive(self):
+    _decoratorlock.acquire()
+    Thread(target=self._doItExclusive).start()
+    # give it a bit of time to check that it's not actually doing anything
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+  def testExclusiveBlocksSharer(self):
+    _decoratorlock.acquire()
+    Thread(target=self._doItSharer).start()
+    time.sleep(0.05)
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+  def testSharerBlocksExclusive(self):
+    _decoratorlock.acquire(shared=1)
+    Thread(target=self._doItExclusive).start()
+    time.sleep(0.05)
+    self.assertRaises(Queue.Empty, self.done.get, True, 0.2)
+    _decoratorlock.release()
+    self.assert_(self.done.get(True, 1))
+
+
 class TestLockSet(unittest.TestCase):
   """LockSet tests"""