/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

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
120
120
        LockBreakMismatch,
121
121
        LockBroken,
122
122
        LockContention,
 
123
        LockCorrupt,
123
124
        LockFailed,
124
125
        LockNotHeld,
125
126
        NoSuchFile,
151
152
# files/dirs created.
152
153
 
153
154
 
154
 
_DEFAULT_TIMEOUT_SECONDS = 300
 
155
_DEFAULT_TIMEOUT_SECONDS = 30
155
156
_DEFAULT_POLL_SECONDS = 1.0
156
157
 
157
158
 
243
244
        # have a similar bug allowing someone to think they got the lock
244
245
        # when it's already held.
245
246
        #
246
 
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
 
247
        # See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
247
248
        #
248
249
        # Strictly the check is unnecessary and a waste of time for most
249
250
        # people, but probably worth trapping if something is wrong.
348
349
        it possibly being still active.
349
350
        """
350
351
        self._check_not_locked()
351
 
        holder_info = self.peek()
 
352
        try:
 
353
            holder_info = self.peek()
 
354
        except LockCorrupt, e:
 
355
            # The lock info is corrupt.
 
356
            if bzrlib.ui.ui_factory.get_boolean("Break (corrupt %r)" % (self,)):
 
357
                self.force_break_corrupt(e.file_data)
 
358
            return
352
359
        if holder_info is not None:
353
360
            lock_info = '\n'.join(self._format_lock_info(holder_info))
354
 
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
361
            if bzrlib.ui.ui_factory.confirm_action(
 
362
                "Break %(lock_info)s", 'bzrlib.lockdir.break', 
 
363
                dict(lock_info=lock_info)):
355
364
                self.force_break(holder_info)
356
365
 
357
366
    def force_break(self, dead_holder_info):
395
404
        for hook in self.hooks['lock_broken']:
396
405
            hook(result)
397
406
 
 
407
    def force_break_corrupt(self, corrupt_info_lines):
 
408
        """Release a lock that has been corrupted.
 
409
        
 
410
        This is very similar to force_break, it except it doesn't assume that
 
411
        self.peek() can work.
 
412
        
 
413
        :param corrupt_info_lines: the lines of the corrupted info file, used
 
414
            to check that the lock hasn't changed between reading the (corrupt)
 
415
            info file and calling force_break_corrupt.
 
416
        """
 
417
        # XXX: this copes with unparseable info files, but what about missing
 
418
        # info files?  Or missing lock dirs?
 
419
        self._check_not_locked()
 
420
        tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
 
421
        self.transport.rename(self._held_dir, tmpname)
 
422
        # check that we actually broke the right lock, not someone else;
 
423
        # there's a small race window between checking it and doing the
 
424
        # rename.
 
425
        broken_info_path = tmpname + self.__INFO_NAME
 
426
        f = self.transport.get(broken_info_path)
 
427
        broken_lines = f.readlines()
 
428
        if broken_lines != corrupt_info_lines:
 
429
            raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
 
430
        self.transport.delete(broken_info_path)
 
431
        self.transport.rmdir(tmpname)
 
432
        result = lock.LockResult(self.transport.abspath(self.path))
 
433
        for hook in self.hooks['lock_broken']:
 
434
            hook(result)
 
435
 
398
436
    def _check_not_locked(self):
399
437
        """If the lock is held by this instance, raise an error."""
400
438
        if self._lock_held:
447
485
        # XXX: is creating this here inefficient?
448
486
        config = bzrlib.config.GlobalConfig()
449
487
        try:
450
 
            user = config.user_email()
451
 
        except errors.NoEmailInUsername:
452
488
            user = config.username()
 
489
        except errors.NoWhoami:
 
490
            user = osutils.getuser_unicode()
453
491
        s = rio.Stanza(hostname=get_host_name(),
454
492
                   pid=str(os.getpid()),
455
493
                   start_time=str(int(time.time())),
459
497
        return s.to_string()
460
498
 
461
499
    def _parse_info(self, info_bytes):
462
 
        stanza = rio.read_stanza(osutils.split_lines(info_bytes))
 
500
        lines = osutils.split_lines(info_bytes)
 
501
        try:
 
502
            stanza = rio.read_stanza(lines)
 
503
        except ValueError, e:
 
504
            mutter('Corrupt lock info file: %r', lines)
 
505
            raise LockCorrupt("could not parse lock info file: " + str(e),
 
506
                              lines)
463
507
        if stanza is None:
464
508
            # see bug 185013; we fairly often end up with the info file being
465
509
            # empty after an interruption; we could log a message here but
539
583
                if deadline_str is None:
540
584
                    deadline_str = time.strftime('%H:%M:%S',
541
585
                                                 time.localtime(deadline))
 
586
                # As local lock urls are correct we display them.
 
587
                # We avoid displaying remote lock urls.
542
588
                lock_url = self.transport.abspath(self.path)
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
 
 
 
589
                if lock_url.startswith('file://'):
 
590
                    lock_url = lock_url.split('.bzr/')[0]
 
591
                else:
 
592
                    lock_url = ''
 
593
                user, hostname, pid, time_ago = formatted_info
 
594
                msg = ('%s lock %s '        # lock_url
 
595
                    'held by '              # start
 
596
                    '%s\n'                  # user
 
597
                    'at %s '                # hostname
 
598
                    '[process #%s], '       # pid
 
599
                    'acquired %s.')         # time ago
 
600
                msg_args = [start, lock_url, user, hostname, pid, time_ago]
 
601
                if timeout > 0:
 
602
                    msg += ('\nWill continue to try until %s, unless '
 
603
                        'you press Ctrl-C.')
 
604
                    msg_args.append(deadline_str)
 
605
                msg += '\nSee "bzr help break-lock" for more.'
 
606
                self._report_function(msg, *msg_args)
560
607
            if (max_attempts is not None) and (attempt_count >= max_attempts):
561
608
                self._trace("exceeded %d attempts")
562
609
                raise LockContention(self)
564
611
                self._trace("waiting %ss", poll)
565
612
                time.sleep(poll)
566
613
            else:
 
614
                # As timeout is always 0 for remote locks
 
615
                # this block is applicable only for local
 
616
                # lock contention
567
617
                self._trace("timeout after waiting %ss", timeout)
568
 
                raise LockContention(self)
 
618
                raise LockContention('(local)', lock_url)
569
619
 
570
620
    def leave_in_place(self):
571
621
        self._locked_via_token = True
616
666
 
617
667
    def _format_lock_info(self, info):
618
668
        """Turn the contents of peek() into something for the user"""
619
 
        lock_url = self.transport.abspath(self.path)
620
669
        start_time = info.get('start_time')
621
670
        if start_time is None:
622
671
            time_ago = '(unknown)'
623
672
        else:
624
673
            time_ago = format_delta(time.time() - int(info['start_time']))
 
674
        user = info.get('user', '<unknown>')
 
675
        hostname = info.get('hostname', '<unknown>')
 
676
        pid = info.get('pid', '<unknown>')
625
677
        return [
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,),
 
678
            user,
 
679
            hostname,
 
680
            pid,
 
681
            time_ago,
630
682
            ]
631
683
 
632
684
    def validate_token(self, token):