1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
1
# Copyright (C) 2006-2010 Canonical Ltd
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
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.
253
254
raise LockFailed(self, "lock was renamed into place, but "
254
255
"now is missing!")
255
if info['nonce'] != self.nonce:
256
if info.get('nonce') != self.nonce:
256
257
self._trace("rename succeeded, "
257
258
"but lock is still held by someone else")
258
259
raise LockContention(self)
346
347
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
347
348
prompt for input if a lock is detected and there is any doubt about
348
349
it possibly being still active.
351
:returns: LockResult for the broken lock.
350
353
self._check_not_locked()
351
holder_info = self.peek()
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)
352
361
if holder_info is not None:
353
362
lock_info = '\n'.join(self._format_lock_info(holder_info))
354
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
355
self.force_break(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)
357
370
def force_break(self, dead_holder_info):
358
371
"""Release a lock held by another process.
369
382
After the lock is broken it will not be held by any process.
370
383
It is possible that another process may sneak in and take the
371
384
lock before the breaking process acquires it.
386
:returns: LockResult for the broken lock.
373
388
if not isinstance(dead_holder_info, dict):
374
389
raise ValueError("dead_holder_info: %r" % dead_holder_info)
394
409
current_info.get('nonce'))
395
410
for hook in self.hooks['lock_broken']:
414
def force_break_corrupt(self, corrupt_info_lines):
415
"""Release a lock that has been corrupted.
417
This is very similar to force_break, it except it doesn't assume that
418
self.peek() can work.
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.
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
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']:
398
443
def _check_not_locked(self):
399
444
"""If the lock is held by this instance, raise an error."""
431
476
"""Check if the lock is held by anyone.
433
If it is held, this returns the lock info structure as a rio Stanza,
478
If it is held, this returns the lock info structure as a dict
434
479
which contains some information about the current lock holder.
435
480
Otherwise returns None.
447
492
# XXX: is creating this here inefficient?
448
493
config = bzrlib.config.GlobalConfig()
450
user = config.user_email()
451
except errors.NoEmailInUsername:
452
495
user = config.username()
496
except errors.NoWhoami:
497
user = osutils.getuser_unicode()
453
498
s = rio.Stanza(hostname=get_host_name(),
454
499
pid=str(os.getpid()),
455
500
start_time=str(int(time.time())),
459
504
return s.to_string()
461
506
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()
507
lines = osutils.split_lines(info_bytes)
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),
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
520
return stanza.as_dict()
465
522
def attempt_lock(self):
466
523
"""Take the lock; fail if it's already held.
533
590
if deadline_str is None:
534
591
deadline_str = time.strftime('%H:%M:%S',
535
592
time.localtime(deadline))
593
# As local lock urls are correct we display them.
594
# We avoid displaying remote lock urls.
536
595
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.
541
self._report_function('%s %s\n'
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.',
596
if lock_url.startswith('file://'):
597
lock_url = lock_url.split('.bzr/')[0]
600
user, hostname, pid, time_ago = formatted_info
601
msg = ('%s lock %s ' # lock_url
605
'[process #%s], ' # pid
606
'acquired %s.') # time ago
607
msg_args = [start, lock_url, user, hostname, pid, time_ago]
609
msg += ('\nWill continue to try until %s, unless '
611
msg_args.append(deadline_str)
612
msg += '\nSee "bzr help break-lock" for more.'
613
self._report_function(msg, *msg_args)
554
614
if (max_attempts is not None) and (attempt_count >= max_attempts):
555
615
self._trace("exceeded %d attempts")
556
616
raise LockContention(self)
558
618
self._trace("waiting %ss", poll)
621
# As timeout is always 0 for remote locks
622
# this block is applicable only for local
561
624
self._trace("timeout after waiting %ss", timeout)
562
raise LockContention(self)
625
raise LockContention('(local)', lock_url)
564
627
def leave_in_place(self):
565
628
self._locked_via_token = True
611
674
def _format_lock_info(self, info):
612
675
"""Turn the contents of peek() into something for the user"""
613
lock_url = self.transport.abspath(self.path)
614
delta = time.time() - int(info['start_time'])
676
start_time = info.get('start_time')
677
if start_time is None:
678
time_ago = '(unknown)'
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>')
616
'lock %s' % (lock_url,),
617
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
618
'locked %s' % (format_delta(delta),),
621
691
def validate_token(self, token):