~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-10 15:46:03 UTC
  • mfrom: (4985.3.21 update)
  • mto: This revision was merged to the branch mainline in revision 5021.
  • Revision ID: v.ladeuil+lp@free.fr-20100210154603-k4no1gvfuqpzrw7p
Update performs two merges in a more logical order but stop on conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""On-disk mutex protecting a resource
18
18
 
21
21
internal locks (such as flock etc) because they can be seen across all
22
22
transports, including http.
23
23
 
24
 
Objects can be read if there is only physical read access; therefore 
 
24
Objects can be read if there is only physical read access; therefore
25
25
readers can never be required to create a lock, though they will
26
26
check whether a writer is using the lock.  Writers can't detect
27
27
whether anyone else is reading from the resource as they write.
56
56
 
57
57
The desired characteristics are:
58
58
 
59
 
* Locks are not reentrant.  (That is, a client that tries to take a 
 
59
* Locks are not reentrant.  (That is, a client that tries to take a
60
60
  lock it already holds may deadlock or fail.)
61
61
* Stale locks can be guessed at by a heuristic
62
62
* Lost locks can be broken by any client
78
78
and deadlocks will likely occur if the locks are aliased.
79
79
 
80
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 
 
81
by a lock holder to check that their lock has not been broken, and to
82
82
update the timestamp within it.
83
83
 
84
84
Example usage:
110
110
    debug,
111
111
    errors,
112
112
    lock,
 
113
    osutils,
113
114
    )
114
115
import bzrlib.config
 
116
from bzrlib.decorators import only_raises
115
117
from bzrlib.errors import (
116
118
        DirectoryNotEmpty,
117
119
        FileExists,
125
127
        ResourceBusy,
126
128
        TransportError,
127
129
        )
128
 
from bzrlib.hooks import Hooks
129
130
from bzrlib.trace import mutter, note
130
131
from bzrlib.osutils import format_delta, rand_chars, get_host_name
131
132
import bzrlib.ui
167
168
 
168
169
        :param transport: Transport which will contain the lock
169
170
 
170
 
        :param path: Path to the lock within the base directory of the 
 
171
        :param path: Path to the lock within the base directory of the
171
172
            transport.
172
173
        """
173
174
        self.transport = transport
192
193
    def create(self, mode=None):
193
194
        """Create the on-disk lock.
194
195
 
195
 
        This is typically only called when the object/directory containing the 
 
196
        This is typically only called when the object/directory containing the
196
197
        directory is first created.  The lock is not held when it's created.
197
198
        """
198
199
        self._trace("create lock directory")
204
205
 
205
206
    def _attempt_lock(self):
206
207
        """Make the pending directory and attempt to rename into place.
207
 
        
 
208
 
208
209
        If the rename succeeds, we read back the info file to check that we
209
210
        really got the lock.
210
211
 
241
242
        # incorrect.  It's possible some other servers or filesystems will
242
243
        # have a similar bug allowing someone to think they got the lock
243
244
        # when it's already held.
 
245
        #
 
246
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
 
247
        #
 
248
        # Strictly the check is unnecessary and a waste of time for most
 
249
        # people, but probably worth trapping if something is wrong.
244
250
        info = self.peek()
245
251
        self._trace("after locking, info=%r", info)
 
252
        if info is None:
 
253
            raise LockFailed(self, "lock was renamed into place, but "
 
254
                "now is missing!")
246
255
        if info['nonce'] != self.nonce:
247
256
            self._trace("rename succeeded, "
248
257
                "but lock is still held by someone else")
255
264
    def _remove_pending_dir(self, tmpname):
256
265
        """Remove the pending directory
257
266
 
258
 
        This is called if we failed to rename into place, so that the pending 
 
267
        This is called if we failed to rename into place, so that the pending
259
268
        dirs don't clutter up the lockdir.
260
269
        """
261
270
        self._trace("remove %s", tmpname)
287
296
                                            info_bytes)
288
297
        return tmpname
289
298
 
 
299
    @only_raises(LockNotHeld, LockBroken)
290
300
    def unlock(self):
291
301
        """Release a held lock
292
302
        """
294
304
            self._fake_read_lock = False
295
305
            return
296
306
        if not self._lock_held:
297
 
            raise LockNotHeld(self)
 
307
            return lock.cant_unlock_not_held(self)
298
308
        if self._locked_via_token:
299
309
            self._locked_via_token = False
300
310
            self._lock_held = False
326
336
            self._trace("... unlock succeeded after %dms",
327
337
                    (time.time() - start_time) * 1000)
328
338
            result = lock.LockResult(self.transport.abspath(self.path),
329
 
                old_nonce)
 
339
                                     old_nonce)
330
340
            for hook in self.hooks['lock_released']:
331
341
                hook(result)
332
342
 
343
353
            lock_info = '\n'.join(self._format_lock_info(holder_info))
344
354
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
345
355
                self.force_break(holder_info)
346
 
        
 
356
 
347
357
    def force_break(self, dead_holder_info):
348
358
        """Release a lock held by another process.
349
359
 
357
367
        LockBreakMismatch is raised.
358
368
 
359
369
        After the lock is broken it will not be held by any process.
360
 
        It is possible that another process may sneak in and take the 
 
370
        It is possible that another process may sneak in and take the
361
371
        lock before the breaking process acquires it.
362
372
        """
363
373
        if not isinstance(dead_holder_info, dict):
372
382
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
373
383
        self.transport.rename(self._held_dir, tmpname)
374
384
        # check that we actually broke the right lock, not someone else;
375
 
        # there's a small race window between checking it and doing the 
 
385
        # there's a small race window between checking it and doing the
376
386
        # rename.
377
387
        broken_info_path = tmpname + self.__INFO_NAME
378
388
        broken_info = self._read_info_file(broken_info_path)
380
390
            raise LockBreakMismatch(self, broken_info, dead_holder_info)
381
391
        self.transport.delete(broken_info_path)
382
392
        self.transport.rmdir(tmpname)
 
393
        result = lock.LockResult(self.transport.abspath(self.path),
 
394
                                 current_info.get('nonce'))
 
395
        for hook in self.hooks['lock_broken']:
 
396
            hook(result)
383
397
 
384
398
    def _check_not_locked(self):
385
399
        """If the lock is held by this instance, raise an error."""
393
407
        or if the lock has been affected by a bug.
394
408
 
395
409
        If the lock is not thought to be held, raises LockNotHeld.  If
396
 
        the lock is thought to be held but has been broken, raises 
 
410
        the lock is thought to be held but has been broken, raises
397
411
        LockBroken.
398
412
        """
399
413
        if not self._lock_held:
405
419
        if info.get('nonce') != self.nonce:
406
420
            # there is a lock, but not ours
407
421
            raise LockBroken(self)
408
 
        
 
422
 
409
423
    def _read_info_file(self, path):
410
424
        """Read one given info file.
411
425
 
412
426
        peek() reads the info file of the lock holder, if any.
413
427
        """
414
 
        return self._parse_info(self.transport.get(path))
 
428
        return self._parse_info(self.transport.get_bytes(path))
415
429
 
416
430
    def peek(self):
417
431
        """Check if the lock is held by anyone.
418
 
        
 
432
 
419
433
        If it is held, this returns the lock info structure as a rio Stanza,
420
434
        which contains some information about the current lock holder.
421
435
        Otherwise returns None.
444
458
                   )
445
459
        return s.to_string()
446
460
 
447
 
    def _parse_info(self, info_file):
448
 
        return rio.read_stanza(info_file.readlines()).as_dict()
 
461
    def _parse_info(self, info_bytes):
 
462
        # TODO: Handle if info_bytes is empty
 
463
        return rio.read_stanza(osutils.split_lines(info_bytes)).as_dict()
449
464
 
450
465
    def attempt_lock(self):
451
466
        """Take the lock; fail if it's already held.
452
 
        
 
467
 
453
468
        If you wish to block until the lock can be obtained, call wait_lock()
454
469
        instead.
455
470
 
476
491
 
477
492
        :param timeout: Approximate maximum amount of time to wait for the
478
493
        lock, in seconds.
479
 
         
 
494
 
480
495
        :param poll: Delay in seconds between retrying the lock.
481
496
 
482
497
        :param max_attempts: Maximum number of times to try to lock.
519
534
                    deadline_str = time.strftime('%H:%M:%S',
520
535
                                                 time.localtime(deadline))
521
536
                lock_url = self.transport.abspath(self.path)
 
537
                # See <https://bugs.edge.launchpad.net/bzr/+bug/250451>
 
538
                # the URL here is sometimes not one that is useful to the
 
539
                # user, perhaps being wrapped in a lp-%d or chroot decorator,
 
540
                # especially if this error is issued from the server.
522
541
                self._report_function('%s %s\n'
523
 
                                      '%s\n' # held by
524
 
                                      '%s\n' # locked ... ago
525
 
                                      'Will continue to try until %s, unless '
526
 
                                      'you press Ctrl-C\n'
527
 
                                      'If you\'re sure that it\'s not being '
528
 
                                      'modified, use bzr break-lock %s',
529
 
                                      start,
530
 
                                      formatted_info[0],
531
 
                                      formatted_info[1],
532
 
                                      formatted_info[2],
533
 
                                      deadline_str,
534
 
                                      lock_url)
 
542
                    '%s\n' # held by
 
543
                    '%s\n' # locked ... ago
 
544
                    'Will continue to try until %s, unless '
 
545
                    'you press Ctrl-C.\n'
 
546
                    'See "bzr help break-lock" for more.',
 
547
                    start,
 
548
                    formatted_info[0],
 
549
                    formatted_info[1],
 
550
                    formatted_info[2],
 
551
                    deadline_str,
 
552
                    )
535
553
 
536
554
            if (max_attempts is not None) and (attempt_count >= max_attempts):
537
555
                self._trace("exceeded %d attempts")
542
560
            else:
543
561
                self._trace("timeout after waiting %ss", timeout)
544
562
                raise LockContention(self)
545
 
    
 
563
 
546
564
    def leave_in_place(self):
547
565
        self._locked_via_token = True
548
566
 
551
569
 
552
570
    def lock_write(self, token=None):
553
571
        """Wait for and acquire the lock.
554
 
        
 
572
 
555
573
        :param token: if this is already locked, then lock_write will fail
556
574
            unless the token matches the existing lock.
557
575
        :returns: a token if this instance supports tokens, otherwise None.
563
581
        A token should be passed in if you know that you have locked the object
564
582
        some other way, and need to synchronise this object's state with that
565
583
        fact.
566
 
         
 
584
 
567
585
        XXX: docstring duplicated from LockableFiles.lock_write.
568
586
        """
569
587
        if token is not None:
578
596
    def lock_read(self):
579
597
        """Compatibility-mode shared lock.
580
598
 
581
 
        LockDir doesn't support shared read-only locks, so this 
 
599
        LockDir doesn't support shared read-only locks, so this
582
600
        just pretends that the lock is taken but really does nothing.
583
601
        """
584
 
        # At the moment Branches are commonly locked for read, but 
 
602
        # At the moment Branches are commonly locked for read, but
585
603
        # we can't rely on that remotely.  Once this is cleaned up,
586
 
        # reenable this warning to prevent it coming back in 
 
604
        # reenable this warning to prevent it coming back in
587
605
        # -- mbp 20060303
588
606
        ## warn("LockDir.lock_read falls back to write lock")
589
607
        if self._lock_held or self._fake_read_lock: