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