216
234
:returns: The nonce of the lock, if it was successfully acquired.
218
:raises LockContention: If the lock is held by someone else. The exception
219
contains the info of the current holder of the lock.
236
:raises LockContention: If the lock is held by someone else. The
237
exception contains the info of the current holder of the lock.
221
239
self._trace("lock_write...")
222
240
start_time = time.time()
224
242
tmpname = self._create_pending_dir()
225
except (errors.TransportError, PathError), e:
243
except (errors.TransportError, PathError) as e:
226
244
self._trace("... failed to create pending dir, %s", e)
227
245
raise LockFailed(self, e)
229
self.transport.rename(tmpname, self._held_dir)
230
except (errors.TransportError, PathError, DirectoryNotEmpty,
231
FileExists, ResourceBusy), e:
232
self._trace("... contention, %s", e)
233
self._remove_pending_dir(tmpname)
234
raise LockContention(self)
236
self._trace("... lock failed, %s", e)
237
self._remove_pending_dir(tmpname)
248
self.transport.rename(tmpname, self._held_dir)
250
except (errors.TransportError, PathError, DirectoryNotEmpty,
251
FileExists, ResourceBusy) as e:
252
self._trace("... contention, %s", e)
253
other_holder = self.peek()
254
self._trace("other holder is %r" % other_holder)
256
self._handle_lock_contention(other_holder)
258
self._remove_pending_dir(tmpname)
260
except Exception as e:
261
self._trace("... lock failed, %s", e)
262
self._remove_pending_dir(tmpname)
239
264
# We must check we really got the lock, because Launchpad's sftp
240
265
# server at one time had a bug were the rename would successfully
241
266
# move the new directory into the existing directory, which was
261
286
(time.time() - start_time) * 1000)
262
287
return self.nonce
289
def _handle_lock_contention(self, other_holder):
290
"""A lock we want to take is held by someone else.
292
This function can: tell the user about it; possibly detect that it's
293
safe or appropriate to steal the lock, or just raise an exception.
295
If this function returns (without raising an exception) the lock will
298
:param other_holder: A LockHeldInfo for the current holder; note that
299
it might be None if the lock can be seen to be held but the info
302
if (other_holder is not None):
303
if other_holder.is_lock_holder_known_dead():
304
if self.get_config().get('locks.steal_dead'):
305
ui.ui_factory.show_user_warning(
307
lock_url=urlutils.join(self.transport.base, self.path),
308
other_holder_info=text_type(other_holder))
309
self.force_break(other_holder)
310
self._trace("stole lock from dead holder")
312
raise LockContention(self)
264
314
def _remove_pending_dir(self, tmpname):
265
315
"""Remove the pending directory
343
393
def break_lock(self):
344
394
"""Break a lock not held by this instance of LockDir.
346
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
396
This is a UI centric function: it uses the ui.ui_factory to
347
397
prompt for input if a lock is detected and there is any doubt about
348
it possibly being still active.
398
it possibly being still active. force_break is the non-interactive
401
:returns: LockResult for the broken lock.
350
403
self._check_not_locked()
351
holder_info = self.peek()
405
holder_info = self.peek()
406
except LockCorrupt as e:
407
# The lock info is corrupt.
408
if ui.ui_factory.get_boolean(u"Break (corrupt %r)" % (self,)):
409
self.force_break_corrupt(e.file_data)
352
411
if holder_info is not None:
353
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)
412
if ui.ui_factory.confirm_action(
413
u"Break %(lock_info)s",
414
'breezy.lockdir.break',
415
dict(lock_info=text_type(holder_info))):
416
result = self.force_break(holder_info)
417
ui.ui_factory.show_message(
418
"Broke lock %s" % result.lock_url)
357
420
def force_break(self, dead_holder_info):
358
421
"""Release a lock held by another process.
361
424
it still thinks it has the lock there will be two concurrent writers.
362
425
In general the user's approval should be sought for lock breaks.
364
dead_holder_info must be the result of a previous LockDir.peek() call;
365
this is used to check that it's still held by the same process that
366
the user decided was dead. If this is not the current holder,
367
LockBreakMismatch is raised.
369
427
After the lock is broken it will not be held by any process.
370
428
It is possible that another process may sneak in and take the
371
429
lock before the breaking process acquires it.
431
:param dead_holder_info:
432
Must be the result of a previous LockDir.peek() call; this is used
433
to check that it's still held by the same process that the user
434
decided was dead. If this is not the current holder,
435
LockBreakMismatch is raised.
437
:returns: LockResult for the broken lock.
373
if not isinstance(dead_holder_info, dict):
439
if not isinstance(dead_holder_info, LockHeldInfo):
374
440
raise ValueError("dead_holder_info: %r" % dead_holder_info)
375
441
self._check_not_locked()
376
442
current_info = self.peek()
394
460
current_info.get('nonce'))
395
461
for hook in self.hooks['lock_broken']:
465
def force_break_corrupt(self, corrupt_info_lines):
466
"""Release a lock that has been corrupted.
468
This is very similar to force_break, it except it doesn't assume that
469
self.peek() can work.
471
:param corrupt_info_lines: the lines of the corrupted info file, used
472
to check that the lock hasn't changed between reading the (corrupt)
473
info file and calling force_break_corrupt.
475
# XXX: this copes with unparseable info files, but what about missing
476
# info files? Or missing lock dirs?
477
self._check_not_locked()
478
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
479
self.transport.rename(self._held_dir, tmpname)
480
# check that we actually broke the right lock, not someone else;
481
# there's a small race window between checking it and doing the
483
broken_info_path = tmpname + self.__INFO_NAME
484
broken_content = self.transport.get_bytes(broken_info_path)
485
broken_lines = osutils.split_lines(broken_content)
486
if broken_lines != corrupt_info_lines:
487
raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
488
self.transport.delete(broken_info_path)
489
self.transport.rmdir(tmpname)
490
result = lock.LockResult(self.transport.abspath(self.path))
491
for hook in self.hooks['lock_broken']:
398
494
def _check_not_locked(self):
399
495
"""If the lock is held by this instance, raise an error."""
438
535
info = self._read_info_file(self._held_info_path)
439
536
self._trace("peek -> held")
441
except NoSuchFile, e:
538
except NoSuchFile as e:
442
539
self._trace("peek -> not held")
444
541
def _prepare_info(self):
445
542
"""Write information about a pending lock to a temporary file.
447
# XXX: is creating this here inefficient?
448
config = bzrlib.config.GlobalConfig()
450
user = config.user_email()
451
except errors.NoEmailInUsername:
452
user = config.username()
453
s = rio.Stanza(hostname=get_host_name(),
454
pid=str(os.getpid()),
455
start_time=str(int(time.time())),
461
def _parse_info(self, info_bytes):
462
stanza = rio.read_stanza(osutils.split_lines(info_bytes))
464
# see bug 185013; we fairly often end up with the info file being
465
# empty after an interruption; we could log a message here but
466
# there may not be much we can say
469
return stanza.as_dict()
471
545
def attempt_lock(self):
472
546
"""Take the lock; fail if it's already held.
531
617
new_info = self.peek()
532
618
if new_info is not None and new_info != last_info:
533
619
if last_info is None:
534
start = 'Unable to obtain'
620
start = gettext('Unable to obtain')
536
start = 'Lock owner changed for'
622
start = gettext('Lock owner changed for')
537
623
last_info = new_info
538
formatted_info = self._format_lock_info(new_info)
624
msg = gettext('{0} lock {1} {2}.').format(start, lock_url,
539
626
if deadline_str is None:
540
627
deadline_str = time.strftime('%H:%M:%S',
541
time.localtime(deadline))
542
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.',
628
time.localtime(deadline))
630
msg += '\n' + gettext(
631
'Will continue to try until %s, unless '
632
'you press Ctrl-C.') % deadline_str
633
msg += '\n' + gettext('See "brz help break-lock" for more.')
634
self._report_function(msg)
560
635
if (max_attempts is not None) and (attempt_count >= max_attempts):
561
636
self._trace("exceeded %d attempts")
562
637
raise LockContention(self)
646
709
if 'lock' not in debug.debug_flags:
648
711
mutter(str(self) + ": " + (format % args))
713
def get_config(self):
714
"""Get the configuration that governs this lockdir."""
715
# XXX: This really should also use the locationconfig at least, but
716
# that seems a bit hard to hook up at the moment. -- mbp 20110329
717
# FIXME: The above is still true ;) -- vila 20110811
718
return config.GlobalStack()
721
class LockHeldInfo(object):
722
"""The information recorded about a held lock.
724
This information is recorded into the lock when it's taken, and it can be
725
read back by any process with access to the lockdir. It can be used, for
726
example, to tell the user who holds the lock, or to try to detect whether
727
the lock holder is still alive.
729
Prior to bzr 2.4 a simple dict was used instead of an object.
732
def __init__(self, info_dict):
733
self.info_dict = info_dict
736
"""Return a debugging representation of this object."""
737
return "%s(%r)" % (self.__class__.__name__, self.info_dict)
739
def __unicode__(self):
740
"""Return a user-oriented description of this object."""
741
d = self.to_readable_dict()
743
u'held by %(user)s on %(hostname)s (process #%(pid)s), '
744
u'acquired %(time_ago)s') % d)
747
__str__ = __unicode__
749
def to_readable_dict(self):
750
"""Turn the holder info into a dict of human-readable attributes.
752
For example, the start time is presented relative to the current time,
753
rather than as seconds since the epoch.
755
Returns a list of [user, hostname, pid, time_ago] all as readable
758
start_time = self.info_dict.get('start_time')
759
if start_time is None:
760
time_ago = '(unknown)'
762
time_ago = format_delta(
763
time.time() - int(self.info_dict['start_time']))
764
user = self.info_dict.get('user', '<unknown>')
765
hostname = self.info_dict.get('hostname', '<unknown>')
766
pid = self.info_dict.get('pid', '<unknown>')
773
def get(self, field_name):
774
"""Return the contents of a field from the lock info, or None."""
775
return self.info_dict.get(field_name)
778
def for_this_process(cls, extra_holder_info):
779
"""Return a new LockHeldInfo for a lock taken by this process.
782
hostname=get_host_name(),
783
pid=str(os.getpid()),
784
nonce=rand_chars(20),
785
start_time=str(int(time.time())),
786
user=get_username_for_lock_info(),
788
if extra_holder_info is not None:
789
info.update(extra_holder_info)
793
s = rio.Stanza(**self.info_dict)
797
def from_info_file_bytes(cls, info_file_bytes):
798
"""Construct from the contents of the held file."""
799
lines = osutils.split_lines(info_file_bytes)
801
stanza = rio.read_stanza(lines)
802
except ValueError as e:
803
mutter('Corrupt lock info file: %r', lines)
804
raise LockCorrupt("could not parse lock info file: " + str(e),
807
# see bug 185013; we fairly often end up with the info file being
808
# empty after an interruption; we could log a message here but
809
# there may not be much we can say
812
return cls(stanza.as_dict())
817
def __eq__(self, other):
818
"""Equality check for lock holders."""
819
if type(self) != type(other):
821
return self.info_dict == other.info_dict
823
def __ne__(self, other):
824
return not self == other
826
def is_locked_by_this_process(self):
827
"""True if this process seems to be the current lock holder."""
829
self.get('hostname') == get_host_name()
830
and self.get('pid') == str(os.getpid())
831
and self.get('user') == get_username_for_lock_info())
833
def is_lock_holder_known_dead(self):
834
"""True if the lock holder process is known to be dead.
836
False if it's either known to be still alive, or if we just can't tell.
838
We can be fairly sure the lock holder is dead if it declared the same
839
hostname and there is no process with the given pid alive. If people
840
have multiple machines with the same hostname this may cause trouble.
842
This doesn't check whether the lock holder is in fact the same process
843
calling this method. (In that case it will return true.)
845
if self.get('hostname') != get_host_name():
847
if self.get('hostname') == 'localhost':
850
if self.get('user') != get_username_for_lock_info():
851
# Could well be another local process by a different user, but
852
# just to be safe we won't conclude about this either.
854
pid_str = self.info_dict.get('pid', None)
856
mutter("no pid recorded in %r" % (self, ))
861
mutter("can't parse pid %r from %r"
864
return osutils.is_local_pid_dead(pid)
867
def get_username_for_lock_info():
868
"""Get a username suitable for putting into a lock.
870
It's ok if what's written here is not a proper email address as long
871
as it gives some clue who the user is.
874
return config.GlobalStack().get('email')
875
except config.NoWhoami:
876
return osutils.getuser_unicode()