243
244
# have a similar bug allowing someone to think they got the lock
244
245
# when it's already held.
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.
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.
350
351
self._check_not_locked()
351
holder_info = self.peek()
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)
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)
357
366
def force_break(self, dead_holder_info):
395
404
for hook in self.hooks['lock_broken']:
407
def force_break_corrupt(self, corrupt_info_lines):
408
"""Release a lock that has been corrupted.
410
This is very similar to force_break, it except it doesn't assume that
411
self.peek() can work.
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.
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
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']:
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()
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()
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)
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),
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'
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.',
589
if lock_url.startswith('file://'):
590
lock_url = lock_url.split('.bzr/')[0]
593
user, hostname, pid, time_ago = formatted_info
594
msg = ('%s lock %s ' # lock_url
598
'[process #%s], ' # pid
599
'acquired %s.') # time ago
600
msg_args = [start, lock_url, user, hostname, pid, time_ago]
602
msg += ('\nWill continue to try until %s, unless '
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)
614
# As timeout is always 0 for remote locks
615
# this block is applicable only for local
567
617
self._trace("timeout after waiting %ss", timeout)
568
raise LockContention(self)
618
raise LockContention('(local)', lock_url)
570
620
def leave_in_place(self):
571
621
self._locked_via_token = True
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)'
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>')
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,),
632
684
def validate_token(self, token):