~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Robert Collins
  • Date: 2010-05-06 07:48:22 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506074822-0bsgf2j4h8jx0xkk
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
our first in-tree matcher. See the module docstring for details.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
120
120
        LockBreakMismatch,
121
121
        LockBroken,
122
122
        LockContention,
123
 
        LockCorrupt,
124
123
        LockFailed,
125
124
        LockNotHeld,
126
125
        NoSuchFile,
152
151
# files/dirs created.
153
152
 
154
153
 
155
 
_DEFAULT_TIMEOUT_SECONDS = 30
 
154
_DEFAULT_TIMEOUT_SECONDS = 300
156
155
_DEFAULT_POLL_SECONDS = 1.0
157
156
 
158
157
 
244
243
        # have a similar bug allowing someone to think they got the lock
245
244
        # when it's already held.
246
245
        #
247
 
        # See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
 
246
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
248
247
        #
249
248
        # Strictly the check is unnecessary and a waste of time for most
250
249
        # people, but probably worth trapping if something is wrong.
347
346
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
348
347
        prompt for input if a lock is detected and there is any doubt about
349
348
        it possibly being still active.
350
 
 
351
 
        :returns: LockResult for the broken lock.
352
349
        """
353
350
        self._check_not_locked()
354
 
        try:
355
 
            holder_info = self.peek()
356
 
        except LockCorrupt, e:
357
 
            # The lock info is corrupt.
358
 
            if bzrlib.ui.ui_factory.get_boolean("Break (corrupt %r)" % (self,)):
359
 
                self.force_break_corrupt(e.file_data)
360
 
            return
 
351
        holder_info = self.peek()
361
352
        if holder_info is not None:
362
353
            lock_info = '\n'.join(self._format_lock_info(holder_info))
363
 
            if bzrlib.ui.ui_factory.confirm_action(
364
 
                "Break %(lock_info)s", 'bzrlib.lockdir.break', 
365
 
                dict(lock_info=lock_info)):
366
 
                result = self.force_break(holder_info)
367
 
                bzrlib.ui.ui_factory.show_message(
368
 
                    "Broke lock %s" % result.lock_url)
 
354
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
355
                self.force_break(holder_info)
369
356
 
370
357
    def force_break(self, dead_holder_info):
371
358
        """Release a lock held by another process.
382
369
        After the lock is broken it will not be held by any process.
383
370
        It is possible that another process may sneak in and take the
384
371
        lock before the breaking process acquires it.
385
 
 
386
 
        :returns: LockResult for the broken lock.
387
372
        """
388
373
        if not isinstance(dead_holder_info, dict):
389
374
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
409
394
                                 current_info.get('nonce'))
410
395
        for hook in self.hooks['lock_broken']:
411
396
            hook(result)
412
 
        return result
413
 
 
414
 
    def force_break_corrupt(self, corrupt_info_lines):
415
 
        """Release a lock that has been corrupted.
416
 
        
417
 
        This is very similar to force_break, it except it doesn't assume that
418
 
        self.peek() can work.
419
 
        
420
 
        :param corrupt_info_lines: the lines of the corrupted info file, used
421
 
            to check that the lock hasn't changed between reading the (corrupt)
422
 
            info file and calling force_break_corrupt.
423
 
        """
424
 
        # XXX: this copes with unparseable info files, but what about missing
425
 
        # info files?  Or missing lock dirs?
426
 
        self._check_not_locked()
427
 
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
428
 
        self.transport.rename(self._held_dir, tmpname)
429
 
        # check that we actually broke the right lock, not someone else;
430
 
        # there's a small race window between checking it and doing the
431
 
        # rename.
432
 
        broken_info_path = tmpname + self.__INFO_NAME
433
 
        broken_content = self.transport.get_bytes(broken_info_path)
434
 
        broken_lines = osutils.split_lines(broken_content)
435
 
        if broken_lines != corrupt_info_lines:
436
 
            raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
437
 
        self.transport.delete(broken_info_path)
438
 
        self.transport.rmdir(tmpname)
439
 
        result = lock.LockResult(self.transport.abspath(self.path))
440
 
        for hook in self.hooks['lock_broken']:
441
 
            hook(result)
442
397
 
443
398
    def _check_not_locked(self):
444
399
        """If the lock is held by this instance, raise an error."""
492
447
        # XXX: is creating this here inefficient?
493
448
        config = bzrlib.config.GlobalConfig()
494
449
        try:
 
450
            user = config.user_email()
 
451
        except errors.NoEmailInUsername:
495
452
            user = config.username()
496
 
        except errors.NoWhoami:
497
 
            user = osutils.getuser_unicode()
498
453
        s = rio.Stanza(hostname=get_host_name(),
499
454
                   pid=str(os.getpid()),
500
455
                   start_time=str(int(time.time())),
504
459
        return s.to_string()
505
460
 
506
461
    def _parse_info(self, info_bytes):
507
 
        lines = osutils.split_lines(info_bytes)
508
 
        try:
509
 
            stanza = rio.read_stanza(lines)
510
 
        except ValueError, e:
511
 
            mutter('Corrupt lock info file: %r', lines)
512
 
            raise LockCorrupt("could not parse lock info file: " + str(e),
513
 
                              lines)
 
462
        stanza = rio.read_stanza(osutils.split_lines(info_bytes))
514
463
        if stanza is None:
515
464
            # see bug 185013; we fairly often end up with the info file being
516
465
            # empty after an interruption; we could log a message here but
590
539
                if deadline_str is None:
591
540
                    deadline_str = time.strftime('%H:%M:%S',
592
541
                                                 time.localtime(deadline))
593
 
                # As local lock urls are correct we display them.
594
 
                # We avoid displaying remote lock urls.
595
542
                lock_url = self.transport.abspath(self.path)
596
 
                if lock_url.startswith('file://'):
597
 
                    lock_url = lock_url.split('.bzr/')[0]
598
 
                else:
599
 
                    lock_url = ''
600
 
                user, hostname, pid, time_ago = formatted_info
601
 
                msg = ('%s lock %s '        # lock_url
602
 
                    'held by '              # start
603
 
                    '%s\n'                  # user
604
 
                    'at %s '                # hostname
605
 
                    '[process #%s], '       # pid
606
 
                    'acquired %s.')         # time ago
607
 
                msg_args = [start, lock_url, user, hostname, pid, time_ago]
608
 
                if timeout > 0:
609
 
                    msg += ('\nWill continue to try until %s, unless '
610
 
                        'you press Ctrl-C.')
611
 
                    msg_args.append(deadline_str)
612
 
                msg += '\nSee "bzr help break-lock" for more.'
613
 
                self._report_function(msg, *msg_args)
 
543
                # See <https://bugs.edge.launchpad.net/bzr/+bug/250451>
 
544
                # the URL here is sometimes not one that is useful to the
 
545
                # user, perhaps being wrapped in a lp-%d or chroot decorator,
 
546
                # especially if this error is issued from the server.
 
547
                self._report_function('%s %s\n'
 
548
                    '%s\n' # held by
 
549
                    '%s\n' # locked ... ago
 
550
                    'Will continue to try until %s, unless '
 
551
                    'you press Ctrl-C.\n'
 
552
                    'See "bzr help break-lock" for more.',
 
553
                    start,
 
554
                    formatted_info[0],
 
555
                    formatted_info[1],
 
556
                    formatted_info[2],
 
557
                    deadline_str,
 
558
                    )
 
559
 
614
560
            if (max_attempts is not None) and (attempt_count >= max_attempts):
615
561
                self._trace("exceeded %d attempts")
616
562
                raise LockContention(self)
618
564
                self._trace("waiting %ss", poll)
619
565
                time.sleep(poll)
620
566
            else:
621
 
                # As timeout is always 0 for remote locks
622
 
                # this block is applicable only for local
623
 
                # lock contention
624
567
                self._trace("timeout after waiting %ss", timeout)
625
 
                raise LockContention('(local)', lock_url)
 
568
                raise LockContention(self)
626
569
 
627
570
    def leave_in_place(self):
628
571
        self._locked_via_token = True
673
616
 
674
617
    def _format_lock_info(self, info):
675
618
        """Turn the contents of peek() into something for the user"""
 
619
        lock_url = self.transport.abspath(self.path)
676
620
        start_time = info.get('start_time')
677
621
        if start_time is None:
678
622
            time_ago = '(unknown)'
679
623
        else:
680
624
            time_ago = format_delta(time.time() - int(info['start_time']))
681
 
        user = info.get('user', '<unknown>')
682
 
        hostname = info.get('hostname', '<unknown>')
683
 
        pid = info.get('pid', '<unknown>')
684
625
        return [
685
 
            user,
686
 
            hostname,
687
 
            pid,
688
 
            time_ago,
 
626
            'lock %s' % (lock_url,),
 
627
            'held by %s on host %s [process #%s]' %
 
628
                tuple([info.get(x, '<unknown>') for x in ['user', 'hostname', 'pid']]),
 
629
            'locked %s' % (time_ago,),
689
630
            ]
690
631
 
691
632
    def validate_token(self, token):