/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Martin Pool
  • Date: 2007-07-03 07:24:42 UTC
  • mto: This revision was merged to the branch mainline in revision 2584.
  • Revision ID: mbp@sourcefrog.net-20070703072442-y3pwex52rrtsa8gg
Better handling in LockDir of rename that moves one directory within another

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""On-disk mutex protecting a resource
 
18
 
 
19
bzr on-disk objects are locked by the existence of a directory with a
 
20
particular name within the control directory.  We use this rather than OS
 
21
internal locks (such as flock etc) because they can be seen across all
 
22
transports, including http.
 
23
 
 
24
Objects can be read if there is only physical read access; therefore 
 
25
readers can never be required to create a lock, though they will
 
26
check whether a writer is using the lock.  Writers can't detect
 
27
whether anyone else is reading from the resource as they write.
 
28
This works because of ordering constraints that make sure readers
 
29
see a consistent view of existing data.
 
30
 
 
31
Waiting for a lock must be done by polling; this can be aborted after
 
32
a timeout.
 
33
 
 
34
Locks must always be explicitly released, typically from a try/finally
 
35
block -- they are not released from a finalizer or when Python
 
36
exits.
 
37
 
 
38
Locks may fail to be released if the process is abruptly terminated
 
39
(machine stop, SIGKILL) or if a remote transport becomes permanently
 
40
disconnected.  There is therefore a method to break an existing lock.
 
41
This should rarely be used, and generally only with user approval.
 
42
Locks contain some information on when the lock was taken and by who
 
43
which may guide in deciding whether it can safely be broken.  (This is
 
44
similar to the messages displayed by emacs and vim.) Note that if the
 
45
lock holder is still alive they will get no notification that the lock
 
46
has been broken and will continue their work -- so it is important to be
 
47
sure they are actually dead.
 
48
 
 
49
A lock is represented on disk by a directory of a particular name,
 
50
containing an information file.  Taking a lock is done by renaming a
 
51
temporary directory into place.  We use temporary directories because
 
52
for all known transports and filesystems we believe that exactly one
 
53
attempt to claim the lock will succeed and the others will fail.  (Files
 
54
won't do because some filesystems or transports only have
 
55
rename-and-overwrite, making it hard to tell who won.)
 
56
 
 
57
The desired characteristics are:
 
58
 
 
59
* Locks are not reentrant.  (That is, a client that tries to take a 
 
60
  lock it already holds may deadlock or fail.)
 
61
* Stale locks can be guessed at by a heuristic
 
62
* Lost locks can be broken by any client
 
63
* Failed lock operations leave little or no mess
 
64
* Deadlocks are avoided by having a timeout always in use, clients
 
65
  desiring indefinite waits can retry or set a silly big timeout.
 
66
 
 
67
Storage formats use the locks, and also need to consider concurrency
 
68
issues underneath the lock.  A format may choose not to use a lock
 
69
at all for some operations.
 
70
 
 
71
LockDirs always operate over a Transport.  The transport may be readonly, in
 
72
which case the lock can be queried but not acquired.
 
73
 
 
74
Locks are identified by a path name, relative to a base transport.
 
75
 
 
76
Calling code will typically want to make sure there is exactly one LockDir
 
77
object per actual lock on disk.  This module does nothing to prevent aliasing
 
78
and deadlocks will likely occur if the locks are aliased.
 
79
 
 
80
In the future we may add a "freshen" method which can be called
 
81
by a lock holder to check that their lock has not been broken, and to 
 
82
update the timestamp within it.
 
83
 
 
84
Example usage:
 
85
 
 
86
>>> from bzrlib.transport.memory import MemoryTransport
 
87
>>> # typically will be obtained from a BzrDir, Branch, etc
 
88
>>> t = MemoryTransport()
 
89
>>> l = LockDir(t, 'sample-lock')
 
90
>>> l.create()
 
91
>>> token = l.wait_lock()
 
92
>>> # do something here
 
93
>>> l.unlock()
 
94
 
 
95
"""
 
96
 
 
97
 
 
98
# TODO: We sometimes have the problem that our attempt to rename '1234' to
 
99
# 'held' fails because the transport server moves into an existing directory,
 
100
# rather than failing the rename.  If we made the info file name the same as
 
101
# the locked directory name we would avoid this problem because moving into
 
102
# the held directory would implicitly clash.  However this would not mesh with
 
103
# the existing locking code and needs a new format of the containing object.
 
104
# -- robertc, mbp 20070628
 
105
 
 
106
import os
 
107
import time
 
108
from cStringIO import StringIO
 
109
 
 
110
from bzrlib import (
 
111
    debug,
 
112
    errors,
 
113
    )
 
114
import bzrlib.config
 
115
from bzrlib.errors import (
 
116
        DirectoryNotEmpty,
 
117
        FileExists,
 
118
        LockBreakMismatch,
 
119
        LockBroken,
 
120
        LockContention,
 
121
        LockNotHeld,
 
122
        NoSuchFile,
 
123
        PathError,
 
124
        ResourceBusy,
 
125
        UnlockableTransport,
 
126
        )
 
127
from bzrlib.trace import mutter, note
 
128
from bzrlib.transport import Transport
 
129
from bzrlib.osutils import rand_chars, format_delta
 
130
from bzrlib.rio import read_stanza, Stanza
 
131
import bzrlib.ui
 
132
 
 
133
 
 
134
# XXX: At the moment there is no consideration of thread safety on LockDir
 
135
# objects.  This should perhaps be updated - e.g. if two threads try to take a
 
136
# lock at the same time they should *both* get it.  But then that's unlikely
 
137
# to be a good idea.
 
138
 
 
139
# TODO: Perhaps store some kind of note like the bzr command line in the lock
 
140
# info?
 
141
 
 
142
# TODO: Some kind of callback run while polling a lock to show progress
 
143
# indicators.
 
144
 
 
145
# TODO: Make sure to pass the right file and directory mode bits to all
 
146
# files/dirs created.
 
147
 
 
148
 
 
149
_DEFAULT_TIMEOUT_SECONDS = 300
 
150
_DEFAULT_POLL_SECONDS = 1.0
 
151
 
 
152
 
 
153
class LockDir(object):
 
154
    """Write-lock guarding access to data."""
 
155
 
 
156
    __INFO_NAME = '/info'
 
157
 
 
158
    def __init__(self, transport, path, file_modebits=0644, dir_modebits=0755):
 
159
        """Create a new LockDir object.
 
160
 
 
161
        The LockDir is initially unlocked - this just creates the object.
 
162
 
 
163
        :param transport: Transport which will contain the lock
 
164
 
 
165
        :param path: Path to the lock within the base directory of the 
 
166
            transport.
 
167
        """
 
168
        assert isinstance(transport, Transport), \
 
169
            ("not a transport: %r" % transport)
 
170
        self.transport = transport
 
171
        self.path = path
 
172
        self._lock_held = False
 
173
        self._locked_via_token = False
 
174
        self._fake_read_lock = False
 
175
        self._held_dir = path + '/held'
 
176
        self._held_info_path = self._held_dir + self.__INFO_NAME
 
177
        self._file_modebits = file_modebits
 
178
        self._dir_modebits = dir_modebits
 
179
 
 
180
        self._report_function = note
 
181
 
 
182
    def __repr__(self):
 
183
        return '%s(%s%s)' % (self.__class__.__name__,
 
184
                             self.transport.base,
 
185
                             self.path)
 
186
 
 
187
    is_held = property(lambda self: self._lock_held)
 
188
 
 
189
    def create(self, mode=None):
 
190
        """Create the on-disk lock.
 
191
 
 
192
        This is typically only called when the object/directory containing the 
 
193
        directory is first created.  The lock is not held when it's created.
 
194
        """
 
195
        if self.transport.is_readonly():
 
196
            raise UnlockableTransport(self.transport)
 
197
        self._trace("create lock directory")
 
198
        self.transport.mkdir(self.path, mode=mode)
 
199
 
 
200
    def _lock_core(self):
 
201
        """Make the pending directory and attempt to rename into place.
 
202
        
 
203
        If the rename succeeds, we read back the info file to check that we
 
204
        really got the lock.
 
205
 
 
206
        If we fail to acquire the lock, this method is responsible for
 
207
        cleaning up the pending directory if possible.  (But it doesn't do
 
208
        that yet.)
 
209
 
 
210
        :returns: The nonce of the lock, if it was successfully acquired.
 
211
 
 
212
        :raises LockContention: If the lock is held by someone else.  The exception
 
213
            contains the info of the current holder of the lock.
 
214
        """
 
215
        try:
 
216
            self._trace("lock_write...")
 
217
            start_time = time.time()
 
218
            tmpname = self._create_pending_dir()
 
219
    
 
220
            self.transport.rename(tmpname, self._held_dir)
 
221
            # We must check we really got the lock, because Launchpad's sftp
 
222
            # server at one time had a bug were the rename would successfully
 
223
            # move the new directory into the existing directory, which was
 
224
            # incorrect.  It's possible some other servers or filesystems will
 
225
            # have a similar bug allowing someone to think they got the lock
 
226
            # when it's already held.
 
227
            info = self.peek()
 
228
            self._trace("after locking, info=%r", info)
 
229
            if info['nonce'] != self.nonce:
 
230
                self._trace("rename succeeded, "
 
231
                    "but lock is still held by someone else")
 
232
                raise LockContention(self)
 
233
            # we don't call confirm here because we don't want to set
 
234
            # _lock_held til we're sure it's true, and because it's really a
 
235
            # problem, not just regular contention, if this fails
 
236
            self._lock_held = True
 
237
            # FIXME: we should remove the pending lock if we fail, 
 
238
            # https://bugs.launchpad.net/bzr/+bug/109169
 
239
        except errors.PermissionDenied:
 
240
            self._trace("... lock failed, permission denied")
 
241
            raise
 
242
        except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
 
243
            self._trace("... contention, %s", e)
 
244
            raise LockContention(self)
 
245
        self._trace("... lock succeeded after %dms",
 
246
                (time.time() - start_time) * 1000)
 
247
        return self.nonce
 
248
 
 
249
    def _create_pending_dir(self):
 
250
        tmpname = '%s/%s.tmp' % (self.path, rand_chars(10))
 
251
        try:
 
252
            self.transport.mkdir(tmpname)
 
253
        except NoSuchFile:
 
254
            # This may raise a FileExists exception
 
255
            # which is okay, it will be caught later and determined
 
256
            # to be a LockContention.
 
257
            self._trace("lock directory does not exist, creating it")
 
258
            self.create(mode=self._dir_modebits)
 
259
            # After creating the lock directory, try again
 
260
            self.transport.mkdir(tmpname)
 
261
        self.nonce = rand_chars(20)
 
262
        info_bytes = self._prepare_info()
 
263
        # We use put_file_non_atomic because we just created a new unique
 
264
        # directory so we don't have to worry about files existing there.
 
265
        # We'll rename the whole directory into place to get atomic
 
266
        # properties
 
267
        self.transport.put_bytes_non_atomic(tmpname + self.__INFO_NAME,
 
268
                                            info_bytes)
 
269
        return tmpname
 
270
 
 
271
    def unlock(self):
 
272
        """Release a held lock
 
273
        """
 
274
        if self._fake_read_lock:
 
275
            self._fake_read_lock = False
 
276
            return
 
277
        if not self._lock_held:
 
278
            raise LockNotHeld(self)
 
279
        if self._locked_via_token:
 
280
            self._locked_via_token = False
 
281
            self._lock_held = False
 
282
        else:
 
283
            # rename before deleting, because we can't atomically remove the
 
284
            # whole tree
 
285
            start_time = time.time()
 
286
            self._trace("unlocking")
 
287
            tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
 
288
            # gotta own it to unlock
 
289
            self.confirm()
 
290
            self.transport.rename(self._held_dir, tmpname)
 
291
            self._lock_held = False
 
292
            self.transport.delete(tmpname + self.__INFO_NAME)
 
293
            try:
 
294
                self.transport.rmdir(tmpname)
 
295
            except DirectoryNotEmpty, e:
 
296
                # There might have been junk left over by a rename that moved
 
297
                # another locker within the 'held' directory.  do a slower
 
298
                # deletion where we list the directory and remove everything
 
299
                # within it.
 
300
                #
 
301
                # Maybe this should be broader to allow for ftp servers with
 
302
                # non-specific error messages?
 
303
                self._trace("doing recursive deletion of non-empty directory "
 
304
                        "%s", tmpname)
 
305
                self.transport.delete_tree(tmpname)
 
306
            self._trace("... unlock succeeded after %dms",
 
307
                    (time.time() - start_time) * 1000)
 
308
 
 
309
    def break_lock(self):
 
310
        """Break a lock not held by this instance of LockDir.
 
311
 
 
312
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
 
313
        prompt for input if a lock is detected and there is any doubt about
 
314
        it possibly being still active.
 
315
        """
 
316
        self._check_not_locked()
 
317
        holder_info = self.peek()
 
318
        if holder_info is not None:
 
319
            lock_info = '\n'.join(self._format_lock_info(holder_info))
 
320
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
321
                self.force_break(holder_info)
 
322
        
 
323
    def force_break(self, dead_holder_info):
 
324
        """Release a lock held by another process.
 
325
 
 
326
        WARNING: This should only be used when the other process is dead; if
 
327
        it still thinks it has the lock there will be two concurrent writers.
 
328
        In general the user's approval should be sought for lock breaks.
 
329
 
 
330
        dead_holder_info must be the result of a previous LockDir.peek() call;
 
331
        this is used to check that it's still held by the same process that
 
332
        the user decided was dead.  If this is not the current holder,
 
333
        LockBreakMismatch is raised.
 
334
 
 
335
        After the lock is broken it will not be held by any process.
 
336
        It is possible that another process may sneak in and take the 
 
337
        lock before the breaking process acquires it.
 
338
        """
 
339
        if not isinstance(dead_holder_info, dict):
 
340
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
 
341
        self._check_not_locked()
 
342
        current_info = self.peek()
 
343
        if current_info is None:
 
344
            # must have been recently released
 
345
            return
 
346
        if current_info != dead_holder_info:
 
347
            raise LockBreakMismatch(self, current_info, dead_holder_info)
 
348
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
 
349
        self.transport.rename(self._held_dir, tmpname)
 
350
        # check that we actually broke the right lock, not someone else;
 
351
        # there's a small race window between checking it and doing the 
 
352
        # rename.
 
353
        broken_info_path = tmpname + self.__INFO_NAME
 
354
        broken_info = self._read_info_file(broken_info_path)
 
355
        if broken_info != dead_holder_info:
 
356
            raise LockBreakMismatch(self, broken_info, dead_holder_info)
 
357
        self.transport.delete(broken_info_path)
 
358
        self.transport.rmdir(tmpname)
 
359
 
 
360
    def _check_not_locked(self):
 
361
        """If the lock is held by this instance, raise an error."""
 
362
        if self._lock_held:
 
363
            raise AssertionError("can't break own lock: %r" % self)
 
364
 
 
365
    def confirm(self):
 
366
        """Make sure that the lock is still held by this locker.
 
367
 
 
368
        This should only fail if the lock was broken by user intervention,
 
369
        or if the lock has been affected by a bug.
 
370
 
 
371
        If the lock is not thought to be held, raises LockNotHeld.  If
 
372
        the lock is thought to be held but has been broken, raises 
 
373
        LockBroken.
 
374
        """
 
375
        if not self._lock_held:
 
376
            raise LockNotHeld(self)
 
377
        info = self.peek()
 
378
        if info is None:
 
379
            # no lock there anymore!
 
380
            raise LockBroken(self)
 
381
        if info.get('nonce') != self.nonce:
 
382
            # there is a lock, but not ours
 
383
            raise LockBroken(self)
 
384
        
 
385
    def _read_info_file(self, path):
 
386
        """Read one given info file.
 
387
 
 
388
        peek() reads the info file of the lock holder, if any.
 
389
        """
 
390
        return self._parse_info(self.transport.get(path))
 
391
 
 
392
    def peek(self):
 
393
        """Check if the lock is held by anyone.
 
394
        
 
395
        If it is held, this returns the lock info structure as a rio Stanza,
 
396
        which contains some information about the current lock holder.
 
397
        Otherwise returns None.
 
398
        """
 
399
        try:
 
400
            info = self._read_info_file(self._held_info_path)
 
401
            self._trace("peek -> held")
 
402
            assert isinstance(info, dict), \
 
403
                    "bad parse result %r" % info
 
404
            return info
 
405
        except NoSuchFile, e:
 
406
            self._trace("peek -> not held")
 
407
 
 
408
    def _prepare_info(self):
 
409
        """Write information about a pending lock to a temporary file.
 
410
        """
 
411
        import socket
 
412
        # XXX: is creating this here inefficient?
 
413
        config = bzrlib.config.GlobalConfig()
 
414
        try:
 
415
            user = config.user_email()
 
416
        except errors.NoEmailInUsername:
 
417
            user = config.username()
 
418
        s = Stanza(hostname=socket.gethostname(),
 
419
                   pid=str(os.getpid()),
 
420
                   start_time=str(int(time.time())),
 
421
                   nonce=self.nonce,
 
422
                   user=user,
 
423
                   )
 
424
        return s.to_string()
 
425
 
 
426
    def _parse_info(self, info_file):
 
427
        return read_stanza(info_file.readlines()).as_dict()
 
428
 
 
429
    def attempt_lock(self):
 
430
        """Take the lock; fail if it's already held.
 
431
        
 
432
        If you wish to block until the lock can be obtained, call wait_lock()
 
433
        instead.
 
434
 
 
435
        :return: The lock token.
 
436
        :raises LockContention: if the lock is held by someone else.
 
437
        """
 
438
        return self.wait_lock(max_attempts=1)
 
439
 
 
440
    def wait_lock(self, timeout=None, poll=None, max_attempts=None):
 
441
        """Wait a certain period for a lock.
 
442
 
 
443
        If the lock can be acquired within the bounded time, it
 
444
        is taken and this returns.  Otherwise, LockContention
 
445
        is raised.  Either way, this function should return within
 
446
        approximately `timeout` seconds.  (It may be a bit more if
 
447
        a transport operation takes a long time to complete.)
 
448
 
 
449
        :param timeout: Approximate maximum amount of time to wait for the
 
450
        lock, in seconds.
 
451
         
 
452
        :param poll: Delay in seconds between retrying the lock.
 
453
 
 
454
        :param max_attempts: Maximum number of times to try to lock.
 
455
 
 
456
        :return: The lock token.
 
457
        """
 
458
        if timeout is None:
 
459
            timeout = _DEFAULT_TIMEOUT_SECONDS
 
460
        if poll is None:
 
461
            poll = _DEFAULT_POLL_SECONDS
 
462
        # XXX: the transport interface doesn't let us guard against operations
 
463
        # there taking a long time, so the total elapsed time or poll interval
 
464
        # may be more than was requested.
 
465
        deadline = time.time() + timeout
 
466
        deadline_str = None
 
467
        last_info = None
 
468
        attempt_count = 0
 
469
        if self._fake_read_lock:
 
470
            raise LockContention(self)
 
471
        if self.transport.is_readonly():
 
472
            raise UnlockableTransport(self.transport)
 
473
        while True:
 
474
            attempt_count += 1
 
475
            try:
 
476
                return self._lock_core()
 
477
            except LockContention, err:
 
478
                # TODO: LockContention should only be raised when we're know
 
479
                # that the lock is held by someone else, in which case we
 
480
                # should include the locker info, so it can be used here.
 
481
                # In other cases, such as having a malformed lock present, we
 
482
                # should raise a different.
 
483
                #
 
484
                # we shouldn't need to peek again here, because _lock_core
 
485
                # does it
 
486
                new_info = self.peek()
 
487
                if new_info is not None and new_info != last_info:
 
488
                    if last_info is None:
 
489
                        start = 'Unable to obtain'
 
490
                    else:
 
491
                        start = 'Lock owner changed for'
 
492
                    last_info = new_info
 
493
                    formatted_info = self._format_lock_info(new_info)
 
494
                    if deadline_str is None:
 
495
                        deadline_str = time.strftime('%H:%M:%S',
 
496
                                                     time.localtime(deadline))
 
497
                    self._report_function('%s %s\n'
 
498
                                          '%s\n' # held by
 
499
                                          '%s\n' # locked ... ago
 
500
                                          'Will continue to try until %s\n',
 
501
                                          start,
 
502
                                          formatted_info[0],
 
503
                                          formatted_info[1],
 
504
                                          formatted_info[2],
 
505
                                          deadline_str)
 
506
 
 
507
                if (max_attempts is not None) and (attempt_count >= max_attempts):
 
508
                    self._trace("exceeded %d attempts")
 
509
                    raise LockContention(self)
 
510
                if time.time() + poll < deadline:
 
511
                    self._trace("waiting %ss", poll)
 
512
                    time.sleep(poll)
 
513
                else:
 
514
                    self._trace("timeout after waiting %ss", timeout)
 
515
                    raise LockContention(self)
 
516
    
 
517
    def leave_in_place(self):
 
518
        self._locked_via_token = True
 
519
 
 
520
    def dont_leave_in_place(self):
 
521
        self._locked_via_token = False
 
522
 
 
523
    def lock_write(self, token=None):
 
524
        """Wait for and acquire the lock.
 
525
        
 
526
        :param token: if this is already locked, then lock_write will fail
 
527
            unless the token matches the existing lock.
 
528
        :returns: a token if this instance supports tokens, otherwise None.
 
529
        :raises TokenLockingNotSupported: when a token is given but this
 
530
            instance doesn't support using token locks.
 
531
        :raises MismatchedToken: if the specified token doesn't match the token
 
532
            of the existing lock.
 
533
 
 
534
        A token should be passed in if you know that you have locked the object
 
535
        some other way, and need to synchronise this object's state with that
 
536
        fact.
 
537
         
 
538
        XXX: docstring duplicated from LockableFiles.lock_write.
 
539
        """
 
540
        if token is not None:
 
541
            self.validate_token(token)
 
542
            self.nonce = token
 
543
            self._lock_held = True
 
544
            self._locked_via_token = True
 
545
            return token
 
546
        else:
 
547
            return self.wait_lock()
 
548
 
 
549
    def lock_read(self):
 
550
        """Compatibility-mode shared lock.
 
551
 
 
552
        LockDir doesn't support shared read-only locks, so this 
 
553
        just pretends that the lock is taken but really does nothing.
 
554
        """
 
555
        # At the moment Branches are commonly locked for read, but 
 
556
        # we can't rely on that remotely.  Once this is cleaned up,
 
557
        # reenable this warning to prevent it coming back in 
 
558
        # -- mbp 20060303
 
559
        ## warn("LockDir.lock_read falls back to write lock")
 
560
        if self._lock_held or self._fake_read_lock:
 
561
            raise LockContention(self)
 
562
        self._fake_read_lock = True
 
563
 
 
564
    def wait(self, timeout=20, poll=0.5):
 
565
        """Wait a certain period for a lock to be released."""
 
566
        # XXX: the transport interface doesn't let us guard 
 
567
        # against operations there taking a long time.
 
568
        #
 
569
        # XXX: Is this really needed?  Do people want to wait for the lock but
 
570
        # not acquire it?  As of bzr 0.17, this seems to only be called from
 
571
        # the test suite.
 
572
        deadline = time.time() + timeout
 
573
        while True:
 
574
            if self.peek():
 
575
                return
 
576
            if time.time() + poll < deadline:
 
577
                self._trace("waiting %ss", poll)
 
578
                time.sleep(poll)
 
579
            else:
 
580
                self._trace("temeout after waiting %ss", timeout)
 
581
                raise LockContention(self)
 
582
 
 
583
    def _format_lock_info(self, info):
 
584
        """Turn the contents of peek() into something for the user"""
 
585
        lock_url = self.transport.abspath(self.path)
 
586
        delta = time.time() - int(info['start_time'])
 
587
        return [
 
588
            'lock %s' % (lock_url,),
 
589
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
 
590
            'locked %s' % (format_delta(delta),),
 
591
            ]
 
592
 
 
593
    def validate_token(self, token):
 
594
        if token is not None:
 
595
            info = self.peek()
 
596
            if info is None:
 
597
                # Lock isn't held
 
598
                lock_token = None
 
599
            else:
 
600
                lock_token = info.get('nonce')
 
601
            if token != lock_token:
 
602
                raise errors.TokenMismatch(token, lock_token)
 
603
            else:
 
604
                self._trace("Revalidated by token %r", token)
 
605
 
 
606
    def _trace(self, format, *args):
 
607
        if 'lock' not in debug.debug_flags:
 
608
            return
 
609
        mutter(str(self) + ": " + (format % args))