Rev 2486: (mbp, r=john) add CountedLock class in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Wed May 16 19:30:58 BST 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 2486
revision-id: pqm at pqm.ubuntu.com-20070516183055-codor2wzlo96i996
parent: pqm at pqm.ubuntu.com-20070510055501-w262sk5hl33vmd19
parent: mbp at sourcefrog.net-20070502140053-50hm0ihm249u627n
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2007-05-16 19:30:55 +0100
message:
  (mbp,r=john) add CountedLock class
added:
  bzrlib/counted_lock.py         counted_lock.py-20070502135927-7dk86io3ok7ctx6k-1
  bzrlib/tests/test_counted_lock.py test_counted_lock.py-20070502135927-7dk86io3ok7ctx6k-2
modified:
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
    ------------------------------------------------------------
    revno: 2475.4.1
    merged: mbp at sourcefrog.net-20070502140053-50hm0ihm249u627n
    parent: pqm at pqm.ubuntu.com-20070501182714-71xp33bziogu3qu0
    committer: Martin Pool <mbp at sourcefrog.net>
    branch nick: counted-lock
    timestamp: Thu 2007-05-03 00:00:53 +1000
    message:
      Start adding CountedLock class to partially replace LockableFiles
=== added file 'bzrlib/counted_lock.py'
--- a/bzrlib/counted_lock.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/counted_lock.py	2007-05-02 14:00:53 +0000
@@ -0,0 +1,87 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Counted lock class"""
+
+
+from bzrlib.errors import (
+    LockError,
+    ReadOnlyError,
+    )
+
+
+# TODO: Pass through lock tokens on lock_write and read, and return them...
+#
+# TODO: Allow upgrading read locks to write?  Conceptually difficult.
+
+
+class CountedLock(object):
+    """Decorator around a lock that makes it reentrant.
+
+    This can be used with any object that provides a basic Lock interface,
+    including LockDirs and OS file locks.
+    """
+
+    def __init__(self, real_lock):
+        self._real_lock = real_lock
+        self._lock_mode = None
+        self._lock_count = 0
+
+    def break_lock(self):
+        self._real_lock.break_lock()
+        self._lock_mode = None
+        self._lock_count = 0
+
+    def is_locked(self):
+        return self._lock_mode is not None
+
+    def lock_read(self):
+        """Acquire the lock in read mode.
+
+        If the lock is already held in either read or write mode this
+        increments the count and succeeds.  If the lock is not already held,
+        it is taken in read mode.
+        """
+        if self._lock_mode:
+            assert self._lock_mode in ('r', 'w'), \
+                   "invalid lock mode %r" % self._lock_mode
+            self._lock_count += 1
+        else:
+            assert self._lock_count == 0
+            self._real_lock.lock_read()
+            self._lock_count = 1
+            self._lock_mode = 'r'
+
+    def lock_write(self):
+        """Acquire the lock in write mode.
+
+        If the lock was originally acquired in read mode this will fail.
+        """
+        if self._lock_count == 0:
+            assert self._lock_mode is None
+            self._real_lock.lock_write()
+            self._lock_mode = 'w'
+        elif self._lock_mode != 'w':
+            raise ReadOnlyError(self)
+        self._lock_count += 1
+
+    def unlock(self):
+        if self._lock_count == 0:
+            raise LockError("%s not locked" % (self,))
+        elif self._lock_count == 1:
+            self._real_lock.unlock()
+            self._lock_mode = None
+        self._lock_count -= 1

=== added file 'bzrlib/tests/test_counted_lock.py'
--- a/bzrlib/tests/test_counted_lock.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/test_counted_lock.py	2007-05-02 14:00:53 +0000
@@ -0,0 +1,170 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests for bzrlib.counted_lock"""
+
+from bzrlib.counted_lock import CountedLock
+from bzrlib.errors import (
+    LockError,
+    ReadOnlyError,
+    )
+from bzrlib.tests import TestCase
+
+
+class DummyLock(object):
+    """Lock that just records what's been done to it."""
+
+    def __init__(self):
+        self._calls = []
+        self._lock_mode = None
+
+    def is_locked(self):
+        return self._lock_mode is not None
+
+    def lock_read(self):
+        self._assert_not_locked()
+        self._lock_mode = 'r'
+        self._calls.append('lock_read')
+
+    def lock_write(self):
+        self._assert_not_locked()
+        self._lock_mode = 'w'
+        self._calls.append('lock_write')
+
+    def unlock(self):
+        self._assert_locked()
+        self._lock_mode = None
+        self._calls.append('unlock')
+
+    def break_lock(self):
+        self._lock_mode = None
+        self._calls.append('break')
+
+    def _assert_locked(self):
+        if not self._lock_mode:
+            raise LockError("%s is not locked" % (self,))
+
+    def _assert_not_locked(self):
+        if self._lock_mode:
+            raise LockError("%s is already locked in mode %r" %
+                (self, self._lock_mode))
+
+
+class TestDummyLock(TestCase):
+
+    def test_lock_initially_not_held(self):
+        l = DummyLock()
+        self.assertFalse(l.is_locked())
+
+    def test_lock_not_reentrant(self):
+        # can't take the underlying lock twice
+        l = DummyLock()
+        l.lock_read()
+        self.assertRaises(LockError, l.lock_read)
+
+    def test_detect_underlock(self):
+        l = DummyLock()
+        self.assertRaises(LockError, l.unlock)
+
+    def test_basic_locking(self):
+        # dummy lock works like a basic non reentrant lock
+        real_lock = DummyLock()
+        self.assertFalse(real_lock.is_locked())
+        # lock read and unlock
+        real_lock.lock_read()
+        self.assertTrue(real_lock.is_locked())
+        real_lock.unlock()
+        self.assertFalse(real_lock.is_locked())
+        # lock write and unlock
+        real_lock.lock_write()
+        self.assertTrue(real_lock.is_locked())
+        real_lock.unlock()
+        self.assertFalse(real_lock.is_locked())
+        # check calls
+        self.assertEqual(
+            ['lock_read', 'unlock', 'lock_write', 'unlock'],
+            real_lock._calls)
+
+    def test_break_lock(self):
+        l = DummyLock()
+        l.lock_write()
+        l.break_lock()
+        self.assertFalse(l.is_locked())
+        self.assertEqual(
+            ['lock_write', 'break'],
+            l._calls)
+
+
+class TestCountedLock(TestCase):
+
+    def test_lock_unlock(self):
+        # Lock and unlock a counted lock
+        real_lock = DummyLock()
+        l = CountedLock(real_lock)
+        self.assertFalse(l.is_locked())
+        # can lock twice, although this isn't allowed on the underlying lock
+        l.lock_read()
+        l.lock_read()
+        self.assertTrue(l.is_locked())
+        # and release
+        l.unlock()
+        self.assertTrue(l.is_locked())
+        l.unlock()
+        self.assertFalse(l.is_locked())
+        self.assertEquals(
+            ['lock_read', 'unlock'],
+            real_lock._calls)
+
+    def test_unlock_not_locked(self):
+        real_lock = DummyLock()
+        l = CountedLock(real_lock)
+        self.assertRaises(LockError, l.unlock)
+
+    def test_read_lock_while_write_locked(self):
+        real_lock = DummyLock()
+        l = CountedLock(real_lock)
+        l.lock_write()
+        l.lock_read()
+        l.lock_write()
+        l.unlock()
+        l.unlock()
+        l.unlock()
+        self.assertFalse(l.is_locked())
+        self.assertEquals(
+            ['lock_write', 'unlock'],
+            real_lock._calls)
+
+    def test_write_lock_while_read_locked(self):
+        real_lock = DummyLock()
+        l = CountedLock(real_lock)
+        l.lock_read()
+        self.assertRaises(ReadOnlyError, l.lock_write)
+        self.assertRaises(ReadOnlyError, l.lock_write)
+        l.unlock()
+        self.assertFalse(l.is_locked())
+        self.assertEquals(
+            ['lock_read', 'unlock'],
+            real_lock._calls)
+
+    def test_break_lock(self):
+        real_lock = DummyLock()
+        l = CountedLock(real_lock)
+        l.lock_write()
+        l.lock_write()
+        self.assertTrue(real_lock.is_locked())
+        l.break_lock()
+        self.assertFalse(l.is_locked())
+        self.assertFalse(real_lock.is_locked())

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2007-05-08 20:00:50 +0000
+++ b/bzrlib/tests/__init__.py	2007-05-16 18:30:55 +0000
@@ -2277,6 +2277,7 @@
                    'bzrlib.tests.test_commit_merge',
                    'bzrlib.tests.test_config',
                    'bzrlib.tests.test_conflicts',
+                   'bzrlib.tests.test_counted_lock',
                    'bzrlib.tests.test_decorators',
                    'bzrlib.tests.test_delta',
                    'bzrlib.tests.test_diff',




More information about the bazaar-commits mailing list