~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockdir.py

  • Committer: Joe Julian
  • Date: 2010-01-10 02:25:31 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: joe@julianfamily.org-20100110022531-wqk61rsagz8xsiga
Added MANIFEST.in to allow bdist_rpm to have all the required include files and tools. bdist_rpm will still fail to build correctly on some distributions due to a disttools bug http://bugs.python.org/issue644744

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
110
110
    debug,
111
111
    errors,
112
112
    lock,
113
 
    osutils,
114
113
    )
115
114
import bzrlib.config
116
 
from bzrlib.decorators import only_raises
117
115
from bzrlib.errors import (
118
116
        DirectoryNotEmpty,
119
117
        FileExists,
120
118
        LockBreakMismatch,
121
119
        LockBroken,
122
120
        LockContention,
123
 
        LockCorrupt,
124
121
        LockFailed,
125
122
        LockNotHeld,
126
123
        NoSuchFile,
152
149
# files/dirs created.
153
150
 
154
151
 
155
 
_DEFAULT_TIMEOUT_SECONDS = 30
 
152
_DEFAULT_TIMEOUT_SECONDS = 300
156
153
_DEFAULT_POLL_SECONDS = 1.0
157
154
 
158
155
 
244
241
        # have a similar bug allowing someone to think they got the lock
245
242
        # when it's already held.
246
243
        #
247
 
        # See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
 
244
        # See <https://bugs.edge.launchpad.net/bzr/+bug/498378> for one case.
248
245
        #
249
246
        # Strictly the check is unnecessary and a waste of time for most
250
247
        # people, but probably worth trapping if something is wrong.
253
250
        if info is None:
254
251
            raise LockFailed(self, "lock was renamed into place, but "
255
252
                "now is missing!")
256
 
        if info.get('nonce') != self.nonce:
 
253
        if info['nonce'] != self.nonce:
257
254
            self._trace("rename succeeded, "
258
255
                "but lock is still held by someone else")
259
256
            raise LockContention(self)
297
294
                                            info_bytes)
298
295
        return tmpname
299
296
 
300
 
    @only_raises(LockNotHeld, LockBroken)
301
297
    def unlock(self):
302
298
        """Release a held lock
303
299
        """
347
343
        This is a UI centric function: it uses the bzrlib.ui.ui_factory to
348
344
        prompt for input if a lock is detected and there is any doubt about
349
345
        it possibly being still active.
350
 
 
351
 
        :returns: LockResult for the broken lock.
352
346
        """
353
347
        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
 
348
        holder_info = self.peek()
361
349
        if holder_info is not None:
362
350
            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)
 
351
            if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
 
352
                self.force_break(holder_info)
369
353
 
370
354
    def force_break(self, dead_holder_info):
371
355
        """Release a lock held by another process.
382
366
        After the lock is broken it will not be held by any process.
383
367
        It is possible that another process may sneak in and take the
384
368
        lock before the breaking process acquires it.
385
 
 
386
 
        :returns: LockResult for the broken lock.
387
369
        """
388
370
        if not isinstance(dead_holder_info, dict):
389
371
            raise ValueError("dead_holder_info: %r" % dead_holder_info)
409
391
                                 current_info.get('nonce'))
410
392
        for hook in self.hooks['lock_broken']:
411
393
            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
 
        f = self.transport.get(broken_info_path)
434
 
        broken_lines = f.readlines()
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
394
 
443
395
    def _check_not_locked(self):
444
396
        """If the lock is held by this instance, raise an error."""
470
422
 
471
423
        peek() reads the info file of the lock holder, if any.
472
424
        """
473
 
        return self._parse_info(self.transport.get_bytes(path))
 
425
        return self._parse_info(self.transport.get(path))
474
426
 
475
427
    def peek(self):
476
428
        """Check if the lock is held by anyone.
477
429
 
478
 
        If it is held, this returns the lock info structure as a dict
 
430
        If it is held, this returns the lock info structure as a rio Stanza,
479
431
        which contains some information about the current lock holder.
480
432
        Otherwise returns None.
481
433
        """
492
444
        # XXX: is creating this here inefficient?
493
445
        config = bzrlib.config.GlobalConfig()
494
446
        try:
 
447
            user = config.user_email()
 
448
        except errors.NoEmailInUsername:
495
449
            user = config.username()
496
 
        except errors.NoWhoami:
497
 
            user = osutils.getuser_unicode()
498
450
        s = rio.Stanza(hostname=get_host_name(),
499
451
                   pid=str(os.getpid()),
500
452
                   start_time=str(int(time.time())),
503
455
                   )
504
456
        return s.to_string()
505
457
 
506
 
    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)
514
 
        if stanza is None:
515
 
            # see bug 185013; we fairly often end up with the info file being
516
 
            # empty after an interruption; we could log a message here but
517
 
            # there may not be much we can say
518
 
            return {}
519
 
        else:
520
 
            return stanza.as_dict()
 
458
    def _parse_info(self, info_file):
 
459
        return rio.read_stanza(info_file.readlines()).as_dict()
521
460
 
522
461
    def attempt_lock(self):
523
462
        """Take the lock; fail if it's already held.
590
529
                if deadline_str is None:
591
530
                    deadline_str = time.strftime('%H:%M:%S',
592
531
                                                 time.localtime(deadline))
593
 
                # As local lock urls are correct we display them.
594
 
                # We avoid displaying remote lock urls.
595
532
                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)
 
533
                self._report_function('%s %s\n'
 
534
                                      '%s\n' # held by
 
535
                                      '%s\n' # locked ... ago
 
536
                                      'Will continue to try until %s, unless '
 
537
                                      'you press Ctrl-C\n'
 
538
                                      'If you\'re sure that it\'s not being '
 
539
                                      'modified, use bzr break-lock %s',
 
540
                                      start,
 
541
                                      formatted_info[0],
 
542
                                      formatted_info[1],
 
543
                                      formatted_info[2],
 
544
                                      deadline_str,
 
545
                                      lock_url)
 
546
 
614
547
            if (max_attempts is not None) and (attempt_count >= max_attempts):
615
548
                self._trace("exceeded %d attempts")
616
549
                raise LockContention(self)
618
551
                self._trace("waiting %ss", poll)
619
552
                time.sleep(poll)
620
553
            else:
621
 
                # As timeout is always 0 for remote locks
622
 
                # this block is applicable only for local
623
 
                # lock contention
624
554
                self._trace("timeout after waiting %ss", timeout)
625
 
                raise LockContention('(local)', lock_url)
 
555
                raise LockContention(self)
626
556
 
627
557
    def leave_in_place(self):
628
558
        self._locked_via_token = True
673
603
 
674
604
    def _format_lock_info(self, info):
675
605
        """Turn the contents of peek() into something for the user"""
676
 
        start_time = info.get('start_time')
677
 
        if start_time is None:
678
 
            time_ago = '(unknown)'
679
 
        else:
680
 
            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>')
 
606
        lock_url = self.transport.abspath(self.path)
 
607
        delta = time.time() - int(info['start_time'])
684
608
        return [
685
 
            user,
686
 
            hostname,
687
 
            pid,
688
 
            time_ago,
 
609
            'lock %s' % (lock_url,),
 
610
            'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
 
611
            'locked %s' % (format_delta(delta),),
689
612
            ]
690
613
 
691
614
    def validate_token(self, token):