1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006, 2007, 2008 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
240
# incorrect. It's possible some other servers or filesystems will
244
241
# have a similar bug allowing someone to think they got the lock
245
242
# 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.
251
243
info = self.peek()
252
244
self._trace("after locking, info=%r", info)
254
raise LockFailed(self, "lock was renamed into place, but "
256
if info.get('nonce') != self.nonce:
245
if info['nonce'] != self.nonce:
257
246
self._trace("rename succeeded, "
258
247
"but lock is still held by someone else")
259
248
raise LockContention(self)
347
335
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
348
336
prompt for input if a lock is detected and there is any doubt about
349
337
it possibly being still active.
351
:returns: LockResult for the broken lock.
353
339
self._check_not_locked()
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)
340
holder_info = self.peek()
361
341
if holder_info is not None:
362
342
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)
343
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
344
self.force_break(holder_info)
370
346
def force_break(self, dead_holder_info):
371
347
"""Release a lock held by another process.
382
358
After the lock is broken it will not be held by any process.
383
359
It is possible that another process may sneak in and take the
384
360
lock before the breaking process acquires it.
386
:returns: LockResult for the broken lock.
388
362
if not isinstance(dead_holder_info, dict):
389
363
raise ValueError("dead_holder_info: %r" % dead_holder_info)
409
383
current_info.get('nonce'))
410
384
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']:
443
387
def _check_not_locked(self):
444
388
"""If the lock is held by this instance, raise an error."""
471
415
peek() reads the info file of the lock holder, if any.
473
return self._parse_info(self.transport.get_bytes(path))
417
return self._parse_info(self.transport.get(path))
476
420
"""Check if the lock is held by anyone.
478
If it is held, this returns the lock info structure as a dict
422
If it is held, this returns the lock info structure as a rio Stanza,
479
423
which contains some information about the current lock holder.
480
424
Otherwise returns None.
492
436
# XXX: is creating this here inefficient?
493
437
config = bzrlib.config.GlobalConfig()
439
user = config.user_email()
440
except errors.NoEmailInUsername:
495
441
user = config.username()
496
except errors.NoWhoami:
497
user = osutils.getuser_unicode()
498
442
s = rio.Stanza(hostname=get_host_name(),
499
443
pid=str(os.getpid()),
500
444
start_time=str(int(time.time())),
504
448
return s.to_string()
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()
450
def _parse_info(self, info_file):
451
return rio.read_stanza(info_file.readlines()).as_dict()
522
453
def attempt_lock(self):
523
454
"""Take the lock; fail if it's already held.
590
521
if deadline_str is None:
591
522
deadline_str = time.strftime('%H:%M:%S',
592
523
time.localtime(deadline))
593
# As local lock urls are correct we display them.
594
# We avoid displaying remote lock urls.
595
524
lock_url = self.transport.abspath(self.path)
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)
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',
614
539
if (max_attempts is not None) and (attempt_count >= max_attempts):
615
540
self._trace("exceeded %d attempts")
616
541
raise LockContention(self)
618
543
self._trace("waiting %ss", poll)
621
# As timeout is always 0 for remote locks
622
# this block is applicable only for local
624
546
self._trace("timeout after waiting %ss", timeout)
625
raise LockContention('(local)', lock_url)
547
raise LockContention(self)
627
549
def leave_in_place(self):
628
550
self._locked_via_token = True
674
596
def _format_lock_info(self, info):
675
597
"""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)'
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>')
598
lock_url = self.transport.abspath(self.path)
599
delta = time.time() - int(info['start_time'])
601
'lock %s' % (lock_url,),
602
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
603
'locked %s' % (format_delta(delta),),
691
606
def validate_token(self, token):