Rev 2414: (Andrew Bennetts, Robert Collins) Add a 'token' argument to lock_write. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Fri Apr 13 06:06:26 BST 2007
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 2414
revision-id: pqm at pqm.ubuntu.com-20070413050623-10v4wozs1tu04kcu
parent: pqm at pqm.ubuntu.com-20070412150356-jeie6iap22sae8xf
parent: andrew.bennetts at canonical.com-20070413010947-wdy5e6gexv20k98b
committer: Canonical.com Patch Queue Manager<pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2007-04-13 06:06:23 +0100
message:
(Andrew Bennetts, Robert Collins) Add a 'token' argument to lock_write.
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/branch.py branch.py-20050309040759-e4baf4e0d046576e
bzrlib/errors.py errors.py-20050309040759-20512168c4e14fbd
bzrlib/lockable_files.py control_files.py-20051111201905-bb88546e799d669f
bzrlib/lockdir.py lockdir.py-20060220222025-98258adf27fbdda3
bzrlib/repository.py rev_storage.py-20051111201905-119e9401e46257e3
bzrlib/tests/branch_implementations/test_locking.py test_locking.py-20060707151933-tav3o2hpibwi53u4-4
bzrlib/tests/lock_helpers.py LockHelpers.py-20060707151933-tav3o2hpibwi53u4-1
bzrlib/tests/repository_implementations/test_repository.py test_repository.py-20060131092128-ad07f494f5c9d26c
bzrlib/tests/test_lockable_files.py test_lockable_files.py-20051225183927-365c7fd99591caf1
------------------------------------------------------------
revno: 2279.7.13
merged: andrew.bennetts at canonical.com-20070413010947-wdy5e6gexv20k98b
parent: andrew.bennetts at canonical.com-20070412082651-cwzuhh5xqwdtchsw
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Fri 2007-04-13 11:09:47 +1000
message:
Add a brief explanation of what tokens are used for to lock_write docstrings.
------------------------------------------------------------
revno: 2279.7.12
merged: andrew.bennetts at canonical.com-20070412082651-cwzuhh5xqwdtchsw
parent: andrew.bennetts at canonical.com-20070412080310-syj0orzfluo3ywb6
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-04-12 18:26:51 +1000
message:
Update NEWS.
------------------------------------------------------------
revno: 2279.7.11
merged: andrew.bennetts at canonical.com-20070412080310-syj0orzfluo3ywb6
parent: andrew.bennetts at canonical.com-20070412074331-otphanb3q0tx6rfz
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-04-12 18:03:10 +1000
message:
Remove some XXXs.
------------------------------------------------------------
revno: 2279.7.10
merged: andrew.bennetts at canonical.com-20070412074331-otphanb3q0tx6rfz
parent: andrew.bennetts at canonical.com-20070412073725-pzd8641vf9oh6v5g
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-04-12 17:43:31 +1000
message:
Change Branch.lock_token to only accept and receive the branch lock token (rather than the branch and repo lock tokens). (copied from hpss branch)
------------------------------------------------------------
revno: 2279.7.9
merged: andrew.bennetts at canonical.com-20070412073725-pzd8641vf9oh6v5g
parent: andrew.bennetts at canonical.com-20070412033450-zbjs6vgxmsw6s7ts
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-04-12 17:37:25 +1000
message:
Remove some redundant code pointed out by Robert's review, and remove some unused imports while I'm there.
------------------------------------------------------------
revno: 2279.7.8
merged: andrew.bennetts at canonical.com-20070412033450-zbjs6vgxmsw6s7ts
parent: andrew.bennetts at canonical.com-20070411133532-u6x6edf3dmzamnaq
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-04-12 13:34:50 +1000
message:
Update NEWS
------------------------------------------------------------
revno: 2279.7.7
merged: andrew.bennetts at canonical.com-20070411133532-u6x6edf3dmzamnaq
parent: andrew.bennetts at canonical.com-20070411064005-zylli6el5cz7kwnb
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Wed 2007-04-11 23:35:32 +1000
message:
LockDir, Repository and Branch lock token changes from the hpss branch.
------------------------------------------------------------
revno: 2279.7.6
merged: andrew.bennetts at canonical.com-20070411064005-zylli6el5cz7kwnb
parent: andrew.bennetts at canonical.com-20070329051401-kqbu8ootnk9dlbgn
parent: pqm at pqm.ubuntu.com-20070411022359-403a2155afb207cf
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Wed 2007-04-11 16:40:05 +1000
message:
Merge from bzr.dev.
------------------------------------------------------------
revno: 2279.7.5
merged: andrew.bennetts at canonical.com-20070329051401-kqbu8ootnk9dlbgn
parent: andrew.bennetts at canonical.com-20070328075403-ibqxtr196emuc4ut
parent: pqm at pqm.ubuntu.com-20070329043540-952aff23533c1c26
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Thu 2007-03-29 15:14:01 +1000
message:
Merge from bzr.dev
------------------------------------------------------------
revno: 2279.7.4
merged: andrew.bennetts at canonical.com-20070328075403-ibqxtr196emuc4ut
parent: andrew.bennetts at canonical.com-20070328073853-yje2ikoflt6a4jos
parent: pqm at pqm.ubuntu.com-20070328065822-999550a858a3ced3
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Wed 2007-03-28 17:54:03 +1000
message:
Merge from bzr.dev.
------------------------------------------------------------
revno: 2279.7.3
merged: andrew.bennetts at canonical.com-20070328073853-yje2ikoflt6a4jos
parent: andrew.bennetts at canonical.com-20070328070842-r843houy668oxb9o
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Wed 2007-03-28 17:38:53 +1000
message:
Some tweaks in response to review.
------------------------------------------------------------
revno: 2279.7.2
merged: andrew.bennetts at canonical.com-20070328070842-r843houy668oxb9o
parent: andrew.bennetts at canonical.com-20070212044932-k9keo85c0s9gg5wv
parent: pqm at pqm.ubuntu.com-20070328022809-40aa40f8edf4e502
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Wed 2007-03-28 17:08:42 +1000
message:
Merge from bzr.dev.
------------------------------------------------------------
revno: 2279.7.1
merged: andrew.bennetts at canonical.com-20070212044932-k9keo85c0s9gg5wv
parent: pqm at pqm.ubuntu.com-20070209195330-312ec52588462782
committer: Andrew Bennetts <andrew.bennetts at canonical.com>
branch nick: bzr.dev.hpss.api.changes
timestamp: Mon 2007-02-12 15:49:32 +1100
message:
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
a seperate LockableFiles instance can share a lock if it has the right token.
(Andrew Bennetts, Robert Collins)
=== modified file 'NEWS'
--- a/NEWS 2007-04-11 22:08:39 +0000
+++ b/NEWS 2007-04-13 05:06:23 +0000
@@ -41,6 +41,11 @@
bzrlib/transport/remote.py contains just the Transport classes that used
to be in bzrlib/transport/smart.py. (Andrew Bennetts)
+ * The ``lock_write`` method of ``LockableFiles``, ``Repository`` and
+ ``Branch`` now accept a ``token`` keyword argument, so that separate
+ instances of those objects can share a lock if it has the right token.
+ (Andrew Bennetts, Robert Collins)
+
BUGFIXES:
* Don't fail bundle selftest if email has 'two' embedded.
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py 2007-04-04 05:19:38 +0000
+++ b/bzrlib/branch.py 2007-04-12 07:43:31 +0000
@@ -195,6 +195,22 @@
def get_physical_lock_status(self):
raise NotImplementedError(self.get_physical_lock_status)
+ def leave_lock_in_place(self):
+ """Tell this branch object not to release the physical lock when this
+ object is unlocked.
+
+ If lock_write doesn't return a token, then this method is not supported.
+ """
+ self.control_files.leave_in_place()
+
+ def dont_leave_lock_in_place(self):
+ """Tell this branch object to release the physical lock when this
+ object is unlocked, even if it didn't originally acquire it.
+
+ If lock_write doesn't return a token, then this method is not supported.
+ """
+ self.control_files.dont_leave_in_place()
+
def abspath(self, name):
"""Return absolute filename for something in the branch
@@ -1223,13 +1239,14 @@
def is_locked(self):
return self.control_files.is_locked()
- def lock_write(self):
- self.repository.lock_write()
+ def lock_write(self, token=None):
+ repo_token = self.repository.lock_write()
try:
- self.control_files.lock_write()
+ token = self.control_files.lock_write(token=token)
except:
self.repository.unlock()
raise
+ return token
def lock_read(self):
self.repository.lock_read()
=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py 2007-04-12 06:34:53 +0000
+++ b/bzrlib/errors.py 2007-04-13 05:06:23 +0000
@@ -809,6 +809,27 @@
self.lock = lock
+class TokenLockingNotSupported(LockError):
+
+ _fmt = "The object %(obj)s does not support token specifying a token when locking."
+
+ internal_error = True
+
+ def __init__(self, obj):
+ self.obj = obj
+
+
+class TokenMismatch(LockBroken):
+
+ _fmt = "The lock token %(given_token)r does not match lock token %(lock_token)r."
+
+ internal_error = True
+
+ def __init__(self, given_token, lock_token):
+ self.given_token = given_token
+ self.lock_token = lock_token
+
+
class PointlessCommit(BzrError):
_fmt = "No changes to commit"
=== modified file 'bzrlib/lockable_files.py'
--- a/bzrlib/lockable_files.py 2007-02-09 23:39:24 +0000
+++ b/bzrlib/lockable_files.py 2007-04-13 01:09:47 +0000
@@ -222,21 +222,47 @@
raise errors.BzrBadParameterNotString(a_string)
self.put_bytes(path, a_string.encode('utf-8'))
- def lock_write(self):
+ def leave_in_place(self):
+ """Set this LockableFiles to not clear the physical lock on unlock."""
+ self._lock.leave_in_place()
+
+ def dont_leave_in_place(self):
+ """Set this LockableFiles to clear the physical lock on unlock."""
+ self._lock.dont_leave_in_place()
+
+ def lock_write(self, token=None):
+ """Lock this group of files for writing.
+
+ :param token: if this is already locked, then lock_write will fail
+ unless the token matches the existing lock.
+ :returns: a token if this instance supports tokens, otherwise None.
+ :raises TokenLockingNotSupported: when a token is given but this
+ instance doesn't support using token locks.
+ :raises MismatchedToken: if the specified token doesn't match the token
+ of the existing lock.
+
+ A token should be passed in if you know that you have locked the object
+ some other way, and need to synchronise this object's state with that
+ fact.
+ """
# mutter("lock write: %s (%s)", self, self._lock_count)
# TODO: Upgrade locking to support using a Transport,
# and potentially a remote locking protocol
if self._lock_mode:
if self._lock_mode != 'w' or not self.get_transaction().writeable():
raise errors.ReadOnlyError(self)
+ self._lock.validate_token(token)
self._lock_count += 1
+ return self._token_from_lock
else:
- self._lock.lock_write()
+ token_from_lock = self._lock.lock_write(token=token)
#note('write locking %s', self)
#traceback.print_stack()
self._lock_mode = 'w'
self._lock_count = 1
self._set_transaction(transactions.WriteTransaction())
+ self._token_from_lock = token_from_lock
+ return token_from_lock
def lock_read(self):
# mutter("lock read: %s (%s)", self, self._lock_count)
@@ -333,7 +359,15 @@
def break_lock(self):
raise NotImplementedError(self.break_lock)
- def lock_write(self):
+ def leave_in_place(self):
+ raise NotImplementedError(self.leave_in_place)
+
+ def dont_leave_in_place(self):
+ raise NotImplementedError(self.dont_leave_in_place)
+
+ def lock_write(self, token=None):
+ if token is not None:
+ raise errors.TokenLockingNotSupported(self)
self._lock = self._transport.lock_write(self._escaped_name)
def lock_read(self):
@@ -351,3 +385,8 @@
# for old-style locks, create the file now
self._transport.put_bytes(self._escaped_name, '',
mode=self._file_modebits)
+
+ def validate_token(self, token):
+ if token is not None:
+ raise errors.TokenLockingNotSupported(self)
+
=== modified file 'bzrlib/lockdir.py'
--- a/bzrlib/lockdir.py 2007-01-31 18:49:12 +0000
+++ b/bzrlib/lockdir.py 2007-04-13 01:09:47 +0000
@@ -160,12 +160,12 @@
self.transport = transport
self.path = path
self._lock_held = False
+ self._locked_via_token = False
self._fake_read_lock = False
self._held_dir = path + '/held'
self._held_info_path = self._held_dir + self.__INFO_NAME
self._file_modebits = file_modebits
self._dir_modebits = dir_modebits
- self.nonce = rand_chars(20)
self._report_function = note
@@ -209,6 +209,7 @@
# After creating the lock directory, try again
self.transport.mkdir(tmpname)
+ self.nonce = rand_chars(20)
info_bytes = self._prepare_info()
# We use put_file_non_atomic because we just created a new unique
# directory so we don't have to worry about files existing there.
@@ -234,15 +235,19 @@
return
if not self._lock_held:
raise LockNotHeld(self)
- # rename before deleting, because we can't atomically remove the whole
- # tree
- tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
- # gotta own it to unlock
- self.confirm()
- self.transport.rename(self._held_dir, tmpname)
- self._lock_held = False
- self.transport.delete(tmpname + self.__INFO_NAME)
- self.transport.rmdir(tmpname)
+ if self._locked_via_token:
+ self._locked_via_token = False
+ self._lock_held = False
+ else:
+ # rename before deleting, because we can't atomically remove the
+ # whole tree
+ tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
+ # gotta own it to unlock
+ self.confirm()
+ self.transport.rename(self._held_dir, tmpname)
+ self._lock_held = False
+ self.transport.delete(tmpname + self.__INFO_NAME)
+ self.transport.rmdir(tmpname)
def break_lock(self):
"""Break a lock not held by this instance of LockDir.
@@ -414,10 +419,39 @@
time.sleep(poll)
else:
raise LockContention(self)
-
- def lock_write(self):
- """Wait for and acquire the lock."""
- self.wait_lock()
+
+ def leave_in_place(self):
+ self._locked_via_token = True
+
+ def dont_leave_in_place(self):
+ self._locked_via_token = False
+
+ def lock_write(self, token=None):
+ """Wait for and acquire the lock.
+
+ :param token: if this is already locked, then lock_write will fail
+ unless the token matches the existing lock.
+ :returns: a token if this instance supports tokens, otherwise None.
+ :raises TokenLockingNotSupported: when a token is given but this
+ instance doesn't support using token locks.
+ :raises MismatchedToken: if the specified token doesn't match the token
+ of the existing lock.
+
+ A token should be passed in if you know that you have locked the object
+ some other way, and need to synchronise this object's state with that
+ fact.
+
+ XXX: docstring duplicated from LockableFiles.lock_write.
+ """
+ if token is not None:
+ self.validate_token(token)
+ self.nonce = token
+ self._lock_held = True
+ self._locked_via_token = True
+ return token
+ else:
+ self.wait_lock()
+ return self.peek().get('nonce')
def lock_read(self):
"""Compatibility-mode shared lock.
@@ -457,3 +491,14 @@
'locked %s' % (format_delta(delta),),
]
+ def validate_token(self, token):
+ if token is not None:
+ info = self.peek()
+ if info is None:
+ # Lock isn't held
+ lock_token = None
+ else:
+ lock_token = info.get('nonce')
+ if token != lock_token:
+ raise errors.TokenMismatch(token, lock_token)
+
=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py 2007-04-11 05:43:39 +0000
+++ b/bzrlib/repository.py 2007-04-13 05:06:23 +0000
@@ -231,8 +231,24 @@
def is_locked(self):
return self.control_files.is_locked()
- def lock_write(self):
- self.control_files.lock_write()
+ def lock_write(self, token=None):
+ """Lock this repository for writing.
+
+ :param token: if this is already locked, then lock_write will fail
+ unless the token matches the existing lock.
+ :returns: a token if this instance supports tokens, otherwise None.
+ :raises TokenLockingNotSupported: when a token is given but this
+ instance doesn't support using token locks.
+ :raises MismatchedToken: if the specified token doesn't match the token
+ of the existing lock.
+
+ A token should be passed in if you know that you have locked the object
+ some other way, and need to synchronise this object's state with that
+ fact.
+
+ XXX: this docstring is duplicated in many places, e.g. lockable_files.py
+ """
+ return self.control_files.lock_write(token=token)
def lock_read(self):
self.control_files.lock_read()
@@ -240,6 +256,22 @@
def get_physical_lock_status(self):
return self.control_files.get_physical_lock_status()
+ def leave_lock_in_place(self):
+ """Tell this repository not to release the physical lock when this
+ object is unlocked.
+
+ If lock_write doesn't return a token, then this method is not supported.
+ """
+ self.control_files.leave_in_place()
+
+ def dont_leave_lock_in_place(self):
+ """Tell this repository to release the physical lock when this
+ object is unlocked, even if it didn't originally acquire it.
+
+ If lock_write doesn't return a token, then this method is not supported.
+ """
+ self.control_files.dont_leave_in_place()
+
@needs_read_lock
def gather_stats(self, revid=None, committers=None):
"""Gather statistics from a revision id.
=== modified file 'bzrlib/tests/branch_implementations/test_locking.py'
--- a/bzrlib/tests/branch_implementations/test_locking.py 2007-03-28 07:33:07 +0000
+++ b/bzrlib/tests/branch_implementations/test_locking.py 2007-04-12 07:43:31 +0000
@@ -227,6 +227,195 @@
('rc', 'ul', True),
], self.locks)
+ def test_lock_write_returns_None_refuses_token(self):
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is not None:
+ # This test does not apply, because this lockable supports
+ # tokens.
+ return
+ self.assertRaises(errors.TokenLockingNotSupported,
+ branch.lock_write, token='token')
+ finally:
+ branch.unlock()
+
+ def test_reentering_lock_write_raises_on_token_mismatch(self):
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ different_branch_token = token + 'xxx'
+ # Re-using the same lockable instance with a different branch token
+ # will raise TokenMismatch.
+ self.assertRaises(errors.TokenMismatch,
+ branch.lock_write,
+ token=different_branch_token)
+ finally:
+ branch.unlock()
+
+ def test_lock_write_with_nonmatching_token(self):
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this branch refuses
+ # tokens.
+ return
+ different_branch_token = token + 'xxx'
+
+ new_branch = branch.bzrdir.open_branch()
+ # We only want to test the relocking abilities of branch, so use the
+ # existing repository object which is already locked.
+ new_branch.repository = branch.repository
+ self.assertRaises(errors.TokenMismatch,
+ new_branch.lock_write,
+ token=different_branch_token)
+ finally:
+ branch.unlock()
+
+
+ def test_lock_write_with_matching_token(self):
+ """Test that a branch can be locked with a token, if it is already
+ locked by that token."""
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this branch refuses tokens.
+ return
+ # The same instance will accept a second lock_write if the specified
+ # token matches.
+ branch.lock_write(token=token)
+ branch.unlock()
+ # Calling lock_write on a new instance for the same lockable will
+ # also succeed.
+ new_branch = branch.bzrdir.open_branch()
+ # We only want to test the relocking abilities of branch, so use the
+ # existing repository object which is already locked.
+ new_branch.repository = branch.repository
+ new_branch.lock_write(token=token)
+ new_branch.unlock()
+ finally:
+ branch.unlock()
+
+ def test_unlock_after_lock_write_with_token(self):
+ # If lock_write did not physically acquire the lock (because it was
+ # passed some tokens), then unlock should not physically release it.
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ new_branch = branch.bzrdir.open_branch()
+ # We only want to test the relocking abilities of branch, so use the
+ # existing repository object which is already locked.
+ new_branch.repository = branch.repository
+ new_branch.lock_write(token=token)
+ new_branch.unlock()
+ self.assertTrue(branch.get_physical_lock_status()) #XXX
+ finally:
+ branch.unlock()
+
+ def test_lock_write_with_token_fails_when_unlocked(self):
+ # First, lock and then unlock to get superficially valid tokens. This
+ # mimics a likely programming error, where a caller accidentally tries
+ # to lock with a token that is no longer valid (because the original
+ # lock was released).
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ branch.unlock()
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+
+ self.assertRaises(errors.TokenMismatch,
+ branch.lock_write, token=token)
+
+ def test_lock_write_reenter_with_token(self):
+ branch = self.make_branch('b')
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ # Relock with a token.
+ branch.lock_write(token=token)
+ branch.unlock()
+ finally:
+ branch.unlock()
+ # The lock should be unlocked on disk. Verify that with a new lock
+ # instance.
+ new_branch = branch.bzrdir.open_branch()
+ # Calling lock_write now should work, rather than raise LockContention.
+ new_branch.lock_write()
+ new_branch.unlock()
+
+ def test_leave_lock_in_place(self):
+ branch = self.make_branch('b')
+ # Lock the branch, then use leave_lock_in_place so that when we
+ # unlock the branch the lock is still held on disk.
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this repository refuses lock
+ # tokens.
+ self.assertRaises(NotImplementedError,
+ branch.leave_lock_in_place)
+ return
+ branch.leave_lock_in_place()
+ finally:
+ branch.unlock()
+ # We should be unable to relock the repo.
+ self.assertRaises(errors.LockContention, branch.lock_write)
+
+ def test_dont_leave_lock_in_place(self):
+ branch = self.make_branch('b')
+ # Create a lock on disk.
+ token = branch.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this branch refuses lock
+ # tokens.
+ self.assertRaises(NotImplementedError,
+ branch.dont_leave_lock_in_place)
+ return
+ try:
+ branch.leave_lock_in_place()
+ except NotImplementedError:
+ # This branch doesn't support this API.
+ return
+ branch.repository.leave_lock_in_place()
+ repo_token = branch.repository.lock_write()
+ branch.repository.unlock()
+ finally:
+ branch.unlock()
+ # Reacquire the lock (with a different branch object) by using the
+ # tokens.
+ new_branch = branch.bzrdir.open_branch()
+ # We have to explicitly lock the repository first.
+ new_branch.repository.lock_write(token=repo_token)
+ new_branch.lock_write(token=token)
+ # Now we don't need our own repository lock anymore (the branch is
+ # holding it for us).
+ new_branch.repository.unlock()
+ # Call dont_leave_lock_in_place, so that the lock will be released by
+ # this instance, even though the lock wasn't originally acquired by it.
+ new_branch.dont_leave_lock_in_place()
+ new_branch.repository.dont_leave_lock_in_place()
+ new_branch.unlock()
+ # Now the branch (and repository) is unlocked. Test this by locking it
+ # without tokens.
+ branch.lock_write()
+ branch.unlock()
+
def test_lock_read_then_unlock(self):
# Calling lock_read then unlocking should work without errors.
branch = self.make_branch('b')
=== modified file 'bzrlib/tests/lock_helpers.py'
--- a/bzrlib/tests/lock_helpers.py 2007-03-28 13:33:58 +0000
+++ b/bzrlib/tests/lock_helpers.py 2007-04-11 13:35:32 +0000
@@ -56,7 +56,7 @@
return self._other.lock_read()
raise TestPreventLocking('lock_read disabled')
- def lock_write(self):
+ def lock_write(self, token=None):
self._sequence.append((self._other_id, 'lw', self._allow_write))
if self._allow_write:
return self._other.lock_write()
=== modified file 'bzrlib/tests/repository_implementations/test_repository.py'
--- a/bzrlib/tests/repository_implementations/test_repository.py 2007-04-11 05:37:53 +0000
+++ b/bzrlib/tests/repository_implementations/test_repository.py 2007-04-13 05:06:23 +0000
@@ -16,31 +16,19 @@
"""Tests for bzrdir implementations - tests a bzrdir format."""
-import os
import re
-import sys
import bzrlib
from bzrlib import (
bzrdir,
errors,
- lockdir,
repository,
)
-from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
from bzrlib.delta import TreeDelta
-from bzrlib.errors import (FileExists,
- NoSuchRevision,
- NoSuchFile,
- UninitializableFormat,
- NotBranchError,
- )
from bzrlib.inventory import Inventory, InventoryDirectory
from bzrlib.revision import NULL_REVISION
-from bzrlib.repofmt import knitrepo
-from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
+from bzrlib.tests import TestCaseWithTransport, TestSkipped
from bzrlib.tests.bzrdir_implementations.test_bzrdir import TestCaseWithBzrDir
-from bzrlib.trace import mutter
from bzrlib.transport import get_transport
from bzrlib.upgrade import upgrade
from bzrlib.workingtree import WorkingTree
@@ -434,6 +422,54 @@
class TestRepositoryLocking(TestCaseWithRepository):
+ def test_leave_lock_in_place(self):
+ repo = self.make_repository('r')
+ # Lock the repository, then use leave_lock_in_place so that when we
+ # unlock the repository the lock is still held on disk.
+ token = repo.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this repository refuses lock
+ # tokens.
+ self.assertRaises(NotImplementedError, repo.leave_lock_in_place)
+ return
+ repo.leave_lock_in_place()
+ finally:
+ repo.unlock()
+ # We should be unable to relock the repo.
+ self.assertRaises(errors.LockContention, repo.lock_write)
+
+ def test_dont_leave_lock_in_place(self):
+ repo = self.make_repository('r')
+ # Create a lock on disk.
+ token = repo.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this repository refuses lock
+ # tokens.
+ self.assertRaises(NotImplementedError,
+ repo.dont_leave_lock_in_place)
+ return
+ try:
+ repo.leave_lock_in_place()
+ except NotImplementedError:
+ # This repository doesn't support this API.
+ return
+ finally:
+ repo.unlock()
+ # Reacquire the lock (with a different repository object) by using the
+ # token.
+ new_repo = repo.bzrdir.open_repository()
+ new_repo.lock_write(token=token)
+ # Call dont_leave_lock_in_place, so that the lock will be released by
+ # this instance, even though the lock wasn't originally acquired by it.
+ new_repo.dont_leave_lock_in_place()
+ new_repo.unlock()
+ # Now the repository is unlocked. Test this by locking it (without a
+ # token).
+ repo.lock_write()
+ repo.unlock()
+
def test_lock_read_then_unlock(self):
# Calling lock_read then unlocking should work without errors.
repo = self.make_repository('r')
@@ -507,7 +543,7 @@
self.assertEqual({'rev1':[],
'rev2':['rev1']},
self.bzrdir.open_repository().get_revision_graph('rev2'))
- self.assertRaises(NoSuchRevision,
+ self.assertRaises(errors.NoSuchRevision,
self.bzrdir.open_repository().get_revision_graph,
'orphan')
# and ghosts are not mentioned
=== modified file 'bzrlib/tests/test_lockable_files.py'
--- a/bzrlib/tests/test_lockable_files.py 2007-02-09 23:39:24 +0000
+++ b/bzrlib/tests/test_lockable_files.py 2007-04-13 01:09:47 +0000
@@ -17,7 +17,6 @@
from StringIO import StringIO
import bzrlib
-from bzrlib.branch import Branch
import bzrlib.errors as errors
from bzrlib.errors import BzrBadParameterNotString, NoSuchFile, ReadOnlyError
from bzrlib.lockable_files import LockableFiles, TransportLock
@@ -125,6 +124,187 @@
self.assertRaises(errors.LockBroken, self.lockable.unlock)
self.assertFalse(self.lockable.is_locked())
+ def test_lock_write_returns_None_refuses_token(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is not None:
+ # This test does not apply, because this lockable supports
+ # tokens.
+ return
+ self.assertRaises(errors.TokenLockingNotSupported,
+ self.lockable.lock_write, token='token')
+ finally:
+ self.lockable.unlock()
+
+ def test_lock_write_returns_token_when_given_token(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ new_lockable = self.get_lockable()
+ token_from_new_lockable = new_lockable.lock_write(token=token)
+ try:
+ self.assertEqual(token, token_from_new_lockable)
+ finally:
+ new_lockable.unlock()
+ finally:
+ self.lockable.unlock()
+
+ def test_lock_write_raises_on_token_mismatch(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ different_token = token + 'xxx'
+ # Re-using the same lockable instance with a different token will
+ # raise TokenMismatch.
+ self.assertRaises(errors.TokenMismatch,
+ self.lockable.lock_write, token=different_token)
+ # A seperate instance for the same lockable will also raise
+ # TokenMismatch.
+ # This detects the case where a caller claims to have a lock (via
+ # the token) for an external resource, but doesn't (the token is
+ # different). Clients need a seperate lock object to make sure the
+ # external resource is probed, whereas the existing lock object
+ # might cache.
+ new_lockable = self.get_lockable()
+ self.assertRaises(errors.TokenMismatch,
+ new_lockable.lock_write, token=different_token)
+ finally:
+ self.lockable.unlock()
+
+ def test_lock_write_with_matching_token(self):
+ # If the token matches, so no exception is raised by lock_write.
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ # The same instance will accept a second lock_write if the specified
+ # token matches.
+ self.lockable.lock_write(token=token)
+ self.lockable.unlock()
+ # Calling lock_write on a new instance for the same lockable will
+ # also succeed.
+ new_lockable = self.get_lockable()
+ new_lockable.lock_write(token=token)
+ new_lockable.unlock()
+ finally:
+ self.lockable.unlock()
+
+ def test_unlock_after_lock_write_with_token(self):
+ # If lock_write did not physically acquire the lock (because it was
+ # passed a token), then unlock should not physically release it.
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ new_lockable = self.get_lockable()
+ new_lockable.lock_write(token=token)
+ new_lockable.unlock()
+ self.assertTrue(self.lockable.get_physical_lock_status())
+ finally:
+ self.lockable.unlock()
+
+ def test_lock_write_with_token_fails_when_unlocked(self):
+ # Lock and unlock to get a superficially valid token. This mimics a
+ # likely programming error, where a caller accidentally tries to lock
+ # with a token that is no longer valid (because the original lock was
+ # released).
+ token = self.lockable.lock_write()
+ self.lockable.unlock()
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+
+ self.assertRaises(errors.TokenMismatch,
+ self.lockable.lock_write, token=token)
+
+ def test_lock_write_reenter_with_token(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ # Relock with a token.
+ token_from_reentry = self.lockable.lock_write(token=token)
+ try:
+ self.assertEqual(token, token_from_reentry)
+ finally:
+ self.lockable.unlock()
+ finally:
+ self.lockable.unlock()
+ # The lock should be unlocked on disk. Verify that with a new lock
+ # instance.
+ new_lockable = self.get_lockable()
+ # Calling lock_write now should work, rather than raise LockContention.
+ new_lockable.lock_write()
+ new_lockable.unlock()
+
+ def test_second_lock_write_returns_same_token(self):
+ first_token = self.lockable.lock_write()
+ try:
+ if first_token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ # Relock the already locked lockable. It should return the same
+ # token.
+ second_token = self.lockable.lock_write()
+ try:
+ self.assertEqual(first_token, second_token)
+ finally:
+ self.lockable.unlock()
+ finally:
+ self.lockable.unlock()
+
+ def test_leave_in_place(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ self.lockable.leave_in_place()
+ finally:
+ self.lockable.unlock()
+ # At this point, the lock is still in place on disk
+ self.assertRaises(errors.LockContention, self.lockable.lock_write)
+ # But should be relockable with a token.
+ self.lockable.lock_write(token=token)
+ self.lockable.unlock()
+
+ def test_dont_leave_in_place(self):
+ token = self.lockable.lock_write()
+ try:
+ if token is None:
+ # This test does not apply, because this lockable refuses
+ # tokens.
+ return
+ self.lockable.leave_in_place()
+ finally:
+ self.lockable.unlock()
+ # At this point, the lock is still in place on disk.
+ # Acquire the existing lock with the token, and ask that it is removed
+ # when this object unlocks, and unlock to trigger that removal.
+ new_lockable = self.get_lockable()
+ new_lockable.lock_write(token=token)
+ new_lockable.dont_leave_in_place()
+ new_lockable.unlock()
+ # At this point, the lock is no longer on disk, so we can lock it.
+ third_lockable = self.get_lockable()
+ third_lockable.lock_write()
+ third_lockable.unlock()
+
# This method of adapting tests to parameters is different to
# the TestProviderAdapters used elsewhere, but seems simpler for this
@@ -133,7 +313,7 @@
_TestLockableFiles_mixin):
def setUp(self):
- super(TestLockableFiles_TransportLock, self).setUp()
+ TestCaseInTempDir.setUp(self)
transport = get_transport('.')
transport.mkdir('.bzr')
self.sub_transport = transport.clone('.bzr')
@@ -155,7 +335,7 @@
"""LockableFile tests run with LockDir underneath"""
def setUp(self):
- super(TestLockableFiles_LockDir, self).setUp()
+ TestCaseInTempDir.setUp(self)
self.transport = get_transport('.')
self.lockable = self.get_lockable()
# the lock creation here sets mode - test_permissions on branch
More information about the bazaar-commits
mailing list