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)
337
325
self._trace("... unlock succeeded after %dms",
338
326
(time.time() - start_time) * 1000)
339
327
result = lock.LockResult(self.transport.abspath(self.path),
341
329
for hook in self.hooks['lock_released']:
349
337
it possibly being still active.
351
339
self._check_not_locked()
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)
340
holder_info = self.peek()
359
341
if holder_info is not None:
360
342
lock_info = '\n'.join(self._format_lock_info(holder_info))
361
if bzrlib.ui.ui_factory.confirm_action(
362
"Break %(lock_info)s", 'bzrlib.lockdir.break',
363
dict(lock_info=lock_info)):
343
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
364
344
self.force_break(holder_info)
366
346
def force_break(self, dead_holder_info):
399
379
raise LockBreakMismatch(self, broken_info, dead_holder_info)
400
380
self.transport.delete(broken_info_path)
401
381
self.transport.rmdir(tmpname)
402
result = lock.LockResult(self.transport.abspath(self.path),
403
current_info.get('nonce'))
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']:
436
383
def _check_not_locked(self):
437
384
"""If the lock is held by this instance, raise an error."""
464
411
peek() reads the info file of the lock holder, if any.
466
return self._parse_info(self.transport.get_bytes(path))
413
return self._parse_info(self.transport.get(path))
469
416
"""Check if the lock is held by anyone.
471
If it is held, this returns the lock info structure as a dict
418
If it is held, this returns the lock info structure as a rio Stanza,
472
419
which contains some information about the current lock holder.
473
420
Otherwise returns None.
485
432
# XXX: is creating this here inefficient?
486
433
config = bzrlib.config.GlobalConfig()
435
user = config.user_email()
436
except errors.NoEmailInUsername:
488
437
user = config.username()
489
except errors.NoWhoami:
490
user = osutils.getuser_unicode()
491
438
s = rio.Stanza(hostname=get_host_name(),
492
439
pid=str(os.getpid()),
493
440
start_time=str(int(time.time())),
497
444
return s.to_string()
499
def _parse_info(self, 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),
508
# see bug 185013; we fairly often end up with the info file being
509
# empty after an interruption; we could log a message here but
510
# there may not be much we can say
513
return stanza.as_dict()
446
def _parse_info(self, info_file):
447
return rio.read_stanza(info_file.readlines()).as_dict()
515
449
def attempt_lock(self):
516
450
"""Take the lock; fail if it's already held.
583
517
if deadline_str is None:
584
518
deadline_str = time.strftime('%H:%M:%S',
585
519
time.localtime(deadline))
586
# As local lock urls are correct we display them.
587
# We avoid displaying remote lock urls.
588
520
lock_url = self.transport.abspath(self.path)
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)
521
self._report_function('%s %s\n'
523
'%s\n' # locked ... ago
524
'Will continue to try until %s, unless '
526
'If you\'re sure that it\'s not being '
527
'modified, use bzr break-lock %s',
607
535
if (max_attempts is not None) and (attempt_count >= max_attempts):
608
536
self._trace("exceeded %d attempts")
609
537
raise LockContention(self)
611
539
self._trace("waiting %ss", poll)
614
# As timeout is always 0 for remote locks
615
# this block is applicable only for local
617
542
self._trace("timeout after waiting %ss", timeout)
618
raise LockContention('(local)', lock_url)
543
raise LockContention(self)
620
545
def leave_in_place(self):
621
546
self._locked_via_token = True
667
592
def _format_lock_info(self, info):
668
593
"""Turn the contents of peek() into something for the user"""
669
start_time = info.get('start_time')
670
if start_time is None:
671
time_ago = '(unknown)'
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>')
594
lock_url = self.transport.abspath(self.path)
595
delta = time.time() - int(info['start_time'])
597
'lock %s' % (lock_url,),
598
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
599
'locked %s' % (format_delta(delta),),
684
602
def validate_token(self, token):