216
231
: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.
233
:raises LockContention: If the lock is held by someone else. The
234
exception contains the info of the current holder of the lock.
221
236
self._trace("lock_write...")
222
237
start_time = time.time()
224
239
tmpname = self._create_pending_dir()
225
except (errors.TransportError, PathError), e:
240
except (errors.TransportError, PathError) as e:
226
241
self._trace("... failed to create pending dir, %s", e)
227
242
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)
245
self.transport.rename(tmpname, self._held_dir)
247
except (errors.TransportError, PathError, DirectoryNotEmpty,
248
FileExists, ResourceBusy) as e:
249
self._trace("... contention, %s", e)
250
other_holder = self.peek()
251
self._trace("other holder is %r" % other_holder)
253
self._handle_lock_contention(other_holder)
255
self._remove_pending_dir(tmpname)
257
except Exception as e:
258
self._trace("... lock failed, %s", e)
259
self._remove_pending_dir(tmpname)
239
261
# We must check we really got the lock, because Launchpad's sftp
240
262
# server at one time had a bug were the rename would successfully
241
263
# move the new directory into the existing directory, which was
261
283
(time.time() - start_time) * 1000)
262
284
return self.nonce
286
def _handle_lock_contention(self, other_holder):
287
"""A lock we want to take is held by someone else.
289
This function can: tell the user about it; possibly detect that it's
290
safe or appropriate to steal the lock, or just raise an exception.
292
If this function returns (without raising an exception) the lock will
295
:param other_holder: A LockHeldInfo for the current holder; note that
296
it might be None if the lock can be seen to be held but the info
299
if (other_holder is not None):
300
if other_holder.is_lock_holder_known_dead():
301
if self.get_config().get('locks.steal_dead'):
302
ui.ui_factory.show_user_warning(
304
lock_url=urlutils.join(self.transport.base, self.path),
305
other_holder_info=text_type(other_holder))
306
self.force_break(other_holder)
307
self._trace("stole lock from dead holder")
309
raise LockContention(self)
264
311
def _remove_pending_dir(self, tmpname):
265
312
"""Remove the pending directory
343
390
def break_lock(self):
344
391
"""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
393
This is a UI centric function: it uses the ui.ui_factory to
347
394
prompt for input if a lock is detected and there is any doubt about
348
it possibly being still active.
395
it possibly being still active. force_break is the non-interactive
398
:returns: LockResult for the broken lock.
350
400
self._check_not_locked()
351
holder_info = self.peek()
402
holder_info = self.peek()
403
except LockCorrupt as e:
404
# The lock info is corrupt.
405
if ui.ui_factory.get_boolean(u"Break (corrupt %r)" % (self,)):
406
self.force_break_corrupt(e.file_data)
352
408
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)
409
if ui.ui_factory.confirm_action(
410
u"Break %(lock_info)s",
411
'breezy.lockdir.break',
412
dict(lock_info=text_type(holder_info))):
413
result = self.force_break(holder_info)
414
ui.ui_factory.show_message(
415
"Broke lock %s" % result.lock_url)
357
417
def force_break(self, dead_holder_info):
358
418
"""Release a lock held by another process.
361
421
it still thinks it has the lock there will be two concurrent writers.
362
422
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
424
After the lock is broken it will not be held by any process.
370
425
It is possible that another process may sneak in and take the
371
426
lock before the breaking process acquires it.
428
:param dead_holder_info:
429
Must be the result of a previous LockDir.peek() call; this is used
430
to check that it's still held by the same process that the user
431
decided was dead. If this is not the current holder,
432
LockBreakMismatch is raised.
434
:returns: LockResult for the broken lock.
373
if not isinstance(dead_holder_info, dict):
436
if not isinstance(dead_holder_info, LockHeldInfo):
374
437
raise ValueError("dead_holder_info: %r" % dead_holder_info)
375
438
self._check_not_locked()
376
439
current_info = self.peek()
394
457
current_info.get('nonce'))
395
458
for hook in self.hooks['lock_broken']:
462
def force_break_corrupt(self, corrupt_info_lines):
463
"""Release a lock that has been corrupted.
465
This is very similar to force_break, it except it doesn't assume that
466
self.peek() can work.
468
:param corrupt_info_lines: the lines of the corrupted info file, used
469
to check that the lock hasn't changed between reading the (corrupt)
470
info file and calling force_break_corrupt.
472
# XXX: this copes with unparseable info files, but what about missing
473
# info files? Or missing lock dirs?
474
self._check_not_locked()
475
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
476
self.transport.rename(self._held_dir, tmpname)
477
# check that we actually broke the right lock, not someone else;
478
# there's a small race window between checking it and doing the
480
broken_info_path = tmpname + self.__INFO_NAME
481
broken_content = self.transport.get_bytes(broken_info_path)
482
broken_lines = osutils.split_lines(broken_content)
483
if broken_lines != corrupt_info_lines:
484
raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
485
self.transport.delete(broken_info_path)
486
self.transport.rmdir(tmpname)
487
result = lock.LockResult(self.transport.abspath(self.path))
488
for hook in self.hooks['lock_broken']:
398
491
def _check_not_locked(self):
399
492
"""If the lock is held by this instance, raise an error."""
438
532
info = self._read_info_file(self._held_info_path)
439
533
self._trace("peek -> held")
441
except NoSuchFile, e:
535
except NoSuchFile as e:
442
536
self._trace("peek -> not held")
444
538
def _prepare_info(self):
445
539
"""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
542
def attempt_lock(self):
472
543
"""Take the lock; fail if it's already held.
531
614
new_info = self.peek()
532
615
if new_info is not None and new_info != last_info:
533
616
if last_info is None:
534
start = 'Unable to obtain'
617
start = gettext('Unable to obtain')
536
start = 'Lock owner changed for'
619
start = gettext('Lock owner changed for')
537
620
last_info = new_info
538
formatted_info = self._format_lock_info(new_info)
621
msg = gettext('{0} lock {1} {2}.').format(start, lock_url,
539
623
if deadline_str is None:
540
624
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.',
625
time.localtime(deadline))
627
msg += '\n' + gettext(
628
'Will continue to try until %s, unless '
629
'you press Ctrl-C.') % deadline_str
630
msg += '\n' + gettext('See "brz help break-lock" for more.')
631
self._report_function(msg)
560
632
if (max_attempts is not None) and (attempt_count >= max_attempts):
561
633
self._trace("exceeded %d attempts")
562
634
raise LockContention(self)
646
706
if 'lock' not in debug.debug_flags:
648
708
mutter(str(self) + ": " + (format % args))
710
def get_config(self):
711
"""Get the configuration that governs this lockdir."""
712
# XXX: This really should also use the locationconfig at least, but
713
# that seems a bit hard to hook up at the moment. -- mbp 20110329
714
# FIXME: The above is still true ;) -- vila 20110811
715
return config.GlobalStack()
718
class LockHeldInfo(object):
719
"""The information recorded about a held lock.
721
This information is recorded into the lock when it's taken, and it can be
722
read back by any process with access to the lockdir. It can be used, for
723
example, to tell the user who holds the lock, or to try to detect whether
724
the lock holder is still alive.
726
Prior to bzr 2.4 a simple dict was used instead of an object.
729
def __init__(self, info_dict):
730
self.info_dict = info_dict
733
"""Return a debugging representation of this object."""
734
return "%s(%r)" % (self.__class__.__name__, self.info_dict)
736
def __unicode__(self):
737
"""Return a user-oriented description of this object."""
738
d = self.to_readable_dict()
740
u'held by %(user)s on %(hostname)s (process #%(pid)s), '
741
u'acquired %(time_ago)s') % d)
744
__str__ = __unicode__
746
def to_readable_dict(self):
747
"""Turn the holder info into a dict of human-readable attributes.
749
For example, the start time is presented relative to the current time,
750
rather than as seconds since the epoch.
752
Returns a list of [user, hostname, pid, time_ago] all as readable
755
start_time = self.info_dict.get('start_time')
756
if start_time is None:
757
time_ago = '(unknown)'
759
time_ago = format_delta(
760
time.time() - int(self.info_dict['start_time']))
761
user = self.info_dict.get('user', '<unknown>')
762
hostname = self.info_dict.get('hostname', '<unknown>')
763
pid = self.info_dict.get('pid', '<unknown>')
770
def get(self, field_name):
771
"""Return the contents of a field from the lock info, or None."""
772
return self.info_dict.get(field_name)
775
def for_this_process(cls, extra_holder_info):
776
"""Return a new LockHeldInfo for a lock taken by this process.
779
hostname=get_host_name(),
780
pid=str(os.getpid()),
781
nonce=rand_chars(20),
782
start_time=str(int(time.time())),
783
user=get_username_for_lock_info(),
785
if extra_holder_info is not None:
786
info.update(extra_holder_info)
790
s = rio.Stanza(**self.info_dict)
794
def from_info_file_bytes(cls, info_file_bytes):
795
"""Construct from the contents of the held file."""
796
lines = osutils.split_lines(info_file_bytes)
798
stanza = rio.read_stanza(lines)
799
except ValueError as e:
800
mutter('Corrupt lock info file: %r', lines)
801
raise LockCorrupt("could not parse lock info file: " + str(e),
804
# see bug 185013; we fairly often end up with the info file being
805
# empty after an interruption; we could log a message here but
806
# there may not be much we can say
809
return cls(stanza.as_dict())
814
def __eq__(self, other):
815
"""Equality check for lock holders."""
816
if type(self) != type(other):
818
return self.info_dict == other.info_dict
820
def __ne__(self, other):
821
return not self == other
823
def is_locked_by_this_process(self):
824
"""True if this process seems to be the current lock holder."""
826
self.get('hostname') == get_host_name()
827
and self.get('pid') == str(os.getpid())
828
and self.get('user') == get_username_for_lock_info())
830
def is_lock_holder_known_dead(self):
831
"""True if the lock holder process is known to be dead.
833
False if it's either known to be still alive, or if we just can't tell.
835
We can be fairly sure the lock holder is dead if it declared the same
836
hostname and there is no process with the given pid alive. If people
837
have multiple machines with the same hostname this may cause trouble.
839
This doesn't check whether the lock holder is in fact the same process
840
calling this method. (In that case it will return true.)
842
if self.get('hostname') != get_host_name():
844
if self.get('hostname') == 'localhost':
847
if self.get('user') != get_username_for_lock_info():
848
# Could well be another local process by a different user, but
849
# just to be safe we won't conclude about this either.
851
pid_str = self.info_dict.get('pid', None)
853
mutter("no pid recorded in %r" % (self, ))
858
mutter("can't parse pid %r from %r"
861
return osutils.is_local_pid_dead(pid)
864
def get_username_for_lock_info():
865
"""Get a username suitable for putting into a lock.
867
It's ok if what's written here is not a proper email address as long
868
as it gives some clue who the user is.
871
return config.GlobalStack().get('email')
872
except config.NoWhoami:
873
return osutils.getuser_unicode()