1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
1
# Copyright (C) 2006-2011 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
240
243
# incorrect. It's possible some other servers or filesystems will
241
244
# have a similar bug allowing someone to think they got the lock
242
245
# when it's already held.
247
# See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
249
# Strictly the check is unnecessary and a waste of time for most
250
# people, but probably worth trapping if something is wrong.
243
251
info = self.peek()
244
252
self._trace("after locking, info=%r", info)
245
if info['nonce'] != self.nonce:
254
raise LockFailed(self, "lock was renamed into place, but "
256
if info.get('nonce') != self.nonce:
246
257
self._trace("rename succeeded, "
247
258
"but lock is still held by someone else")
248
259
raise LockContention(self)
335
347
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
336
348
prompt for input if a lock is detected and there is any doubt about
337
349
it possibly being still active.
351
:returns: LockResult for the broken lock.
339
353
self._check_not_locked()
340
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)
341
361
if holder_info is not None:
342
362
lock_info = '\n'.join(self._format_lock_info(holder_info))
343
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
344
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)
346
370
def force_break(self, dead_holder_info):
347
371
"""Release a lock held by another process.
358
382
After the lock is broken it will not be held by any process.
359
383
It is possible that another process may sneak in and take the
360
384
lock before the breaking process acquires it.
386
:returns: LockResult for the broken lock.
362
388
if not isinstance(dead_holder_info, dict):
363
389
raise ValueError("dead_holder_info: %r" % dead_holder_info)
383
409
current_info.get('nonce'))
384
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
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']:
387
443
def _check_not_locked(self):
388
444
"""If the lock is held by this instance, raise an error."""
415
471
peek() reads the info file of the lock holder, if any.
417
return self._parse_info(self.transport.get(path))
473
return self._parse_info(self.transport.get_bytes(path))
420
476
"""Check if the lock is held by anyone.
422
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
423
479
which contains some information about the current lock holder.
424
480
Otherwise returns None.
436
492
# XXX: is creating this here inefficient?
437
493
config = bzrlib.config.GlobalConfig()
439
user = config.user_email()
440
except errors.NoEmailInUsername:
441
495
user = config.username()
496
except errors.NoWhoami:
497
user = osutils.getuser_unicode()
442
498
s = rio.Stanza(hostname=get_host_name(),
443
499
pid=str(os.getpid()),
444
500
start_time=str(int(time.time())),
448
504
return s.to_string()
450
def _parse_info(self, info_file):
451
return rio.read_stanza(info_file.readlines()).as_dict()
506
def _parse_info(self, info_bytes):
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()
453
522
def attempt_lock(self):
454
523
"""Take the lock; fail if it's already held.
468
537
hook(hook_result)
540
def lock_url_for_display(self):
541
"""Give a nicely-printable representation of the URL of this lock."""
542
# As local lock urls are correct we display them.
543
# We avoid displaying remote lock urls.
544
lock_url = self.transport.abspath(self.path)
545
if lock_url.startswith('file://'):
546
lock_url = lock_url.split('.bzr/')[0]
471
551
def wait_lock(self, timeout=None, poll=None, max_attempts=None):
472
552
"""Wait a certain period for a lock.
521
602
if deadline_str is None:
522
603
deadline_str = time.strftime('%H:%M:%S',
523
604
time.localtime(deadline))
524
lock_url = self.transport.abspath(self.path)
525
self._report_function('%s %s\n'
527
'%s\n' # locked ... ago
528
'Will continue to try until %s, unless '
530
'If you\'re sure that it\'s not being '
531
'modified, use bzr break-lock %s',
605
user, hostname, pid, time_ago = formatted_info
606
msg = ('%s lock %s ' # lock_url
610
'[process #%s], ' # pid
611
'acquired %s.') # time ago
612
msg_args = [start, lock_url, user, hostname, pid, time_ago]
614
msg += ('\nWill continue to try until %s, unless '
616
msg_args.append(deadline_str)
617
msg += '\nSee "bzr help break-lock" for more.'
618
self._report_function(msg, *msg_args)
539
619
if (max_attempts is not None) and (attempt_count >= max_attempts):
540
620
self._trace("exceeded %d attempts")
541
621
raise LockContention(self)
543
623
self._trace("waiting %ss", poll)
626
# As timeout is always 0 for remote locks
627
# this block is applicable only for local
546
629
self._trace("timeout after waiting %ss", timeout)
547
raise LockContention(self)
630
raise LockContention('(local)', lock_url)
549
632
def leave_in_place(self):
550
633
self._locked_via_token = True
596
679
def _format_lock_info(self, info):
597
680
"""Turn the contents of peek() into something for the user"""
598
lock_url = self.transport.abspath(self.path)
599
delta = time.time() - int(info['start_time'])
681
start_time = info.get('start_time')
682
if start_time is None:
683
time_ago = '(unknown)'
685
time_ago = format_delta(time.time() - int(info['start_time']))
686
user = info.get('user', '<unknown>')
687
hostname = info.get('hostname', '<unknown>')
688
pid = info.get('pid', '<unknown>')
601
'lock %s' % (lock_url,),
602
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
603
'locked %s' % (format_delta(delta),),
606
696
def validate_token(self, token):