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