216
225
: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.
227
:raises LockContention: If the lock is held by someone else. The
228
exception contains the info of the current holder of the lock.
221
230
self._trace("lock_write...")
222
231
start_time = time.time()
224
233
tmpname = self._create_pending_dir()
225
except (errors.TransportError, PathError), e:
234
except (errors.TransportError, PathError) as e:
226
235
self._trace("... failed to create pending dir, %s", e)
227
236
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)
239
self.transport.rename(tmpname, self._held_dir)
241
except (errors.TransportError, PathError, DirectoryNotEmpty,
242
FileExists, ResourceBusy) as e:
243
self._trace("... contention, %s", e)
244
other_holder = self.peek()
245
self._trace("other holder is %r" % other_holder)
247
self._handle_lock_contention(other_holder)
248
except BaseException:
249
self._remove_pending_dir(tmpname)
251
except Exception as e:
252
self._trace("... lock failed, %s", e)
253
self._remove_pending_dir(tmpname)
239
255
# We must check we really got the lock, because Launchpad's sftp
240
256
# server at one time had a bug were the rename would successfully
241
257
# move the new directory into the existing directory, which was
251
267
self._trace("after locking, info=%r", info)
253
269
raise LockFailed(self, "lock was renamed into place, but "
255
271
if info.get('nonce') != self.nonce:
256
272
self._trace("rename succeeded, "
257
"but lock is still held by someone else")
273
"but lock is still held by someone else")
258
274
raise LockContention(self)
259
275
self._lock_held = True
260
276
self._trace("... lock succeeded after %dms",
261
(time.time() - start_time) * 1000)
277
(time.time() - start_time) * 1000)
262
278
return self.nonce
280
def _handle_lock_contention(self, other_holder):
281
"""A lock we want to take is held by someone else.
283
This function can: tell the user about it; possibly detect that it's
284
safe or appropriate to steal the lock, or just raise an exception.
286
If this function returns (without raising an exception) the lock will
289
:param other_holder: A LockHeldInfo for the current holder; note that
290
it might be None if the lock can be seen to be held but the info
293
if (other_holder is not None):
294
if other_holder.is_lock_holder_known_dead():
295
if self.get_config().get('locks.steal_dead'):
296
ui.ui_factory.show_user_warning(
298
lock_url=urlutils.join(self.transport.base, self.path),
299
other_holder_info=str(other_holder))
300
self.force_break(other_holder)
301
self._trace("stole lock from dead holder")
303
raise LockContention(self)
264
305
def _remove_pending_dir(self, tmpname):
265
306
"""Remove the pending directory
322
363
self.transport.delete(tmpname + self.__INFO_NAME)
324
365
self.transport.rmdir(tmpname)
325
except DirectoryNotEmpty, e:
366
except DirectoryNotEmpty:
326
367
# There might have been junk left over by a rename that moved
327
368
# another locker within the 'held' directory. do a slower
328
369
# deletion where we list the directory and remove everything
331
# Maybe this should be broader to allow for ftp servers with
332
# non-specific error messages?
333
371
self._trace("doing recursive deletion of non-empty directory "
335
373
self.transport.delete_tree(tmpname)
336
374
self._trace("... unlock succeeded after %dms",
337
(time.time() - start_time) * 1000)
375
(time.time() - start_time) * 1000)
338
376
result = lock.LockResult(self.transport.abspath(self.path),
340
378
for hook in self.hooks['lock_released']:
343
381
def break_lock(self):
344
382
"""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
384
This is a UI centric function: it uses the ui.ui_factory to
347
385
prompt for input if a lock is detected and there is any doubt about
348
it possibly being still active.
386
it possibly being still active. force_break is the non-interactive
389
:returns: LockResult for the broken lock.
350
391
self._check_not_locked()
351
holder_info = self.peek()
393
holder_info = self.peek()
394
except LockCorrupt as e:
395
# The lock info is corrupt.
396
if ui.ui_factory.get_boolean(u"Break (corrupt %r)" % (self,)):
397
self.force_break_corrupt(e.file_data)
352
399
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)
400
if ui.ui_factory.confirm_action(
401
u"Break %(lock_info)s",
402
'breezy.lockdir.break',
403
dict(lock_info=str(holder_info))):
404
result = self.force_break(holder_info)
405
ui.ui_factory.show_message(
406
"Broke lock %s" % result.lock_url)
357
408
def force_break(self, dead_holder_info):
358
409
"""Release a lock held by another process.
361
412
it still thinks it has the lock there will be two concurrent writers.
362
413
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
415
After the lock is broken it will not be held by any process.
370
416
It is possible that another process may sneak in and take the
371
417
lock before the breaking process acquires it.
419
:param dead_holder_info:
420
Must be the result of a previous LockDir.peek() call; this is used
421
to check that it's still held by the same process that the user
422
decided was dead. If this is not the current holder,
423
LockBreakMismatch is raised.
425
:returns: LockResult for the broken lock.
373
if not isinstance(dead_holder_info, dict):
427
if not isinstance(dead_holder_info, LockHeldInfo):
374
428
raise ValueError("dead_holder_info: %r" % dead_holder_info)
375
429
self._check_not_locked()
376
430
current_info = self.peek()
394
448
current_info.get('nonce'))
395
449
for hook in self.hooks['lock_broken']:
453
def force_break_corrupt(self, corrupt_info_lines):
454
"""Release a lock that has been corrupted.
456
This is very similar to force_break, it except it doesn't assume that
457
self.peek() can work.
459
:param corrupt_info_lines: the lines of the corrupted info file, used
460
to check that the lock hasn't changed between reading the (corrupt)
461
info file and calling force_break_corrupt.
463
# XXX: this copes with unparseable info files, but what about missing
464
# info files? Or missing lock dirs?
465
self._check_not_locked()
466
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
467
self.transport.rename(self._held_dir, tmpname)
468
# check that we actually broke the right lock, not someone else;
469
# there's a small race window between checking it and doing the
471
broken_info_path = tmpname + self.__INFO_NAME
472
broken_content = self.transport.get_bytes(broken_info_path)
473
broken_lines = osutils.split_lines(broken_content)
474
if broken_lines != corrupt_info_lines:
475
raise LockBreakMismatch(self, broken_lines, corrupt_info_lines)
476
self.transport.delete(broken_info_path)
477
self.transport.rmdir(tmpname)
478
result = lock.LockResult(self.transport.abspath(self.path))
479
for hook in self.hooks['lock_broken']:
398
482
def _check_not_locked(self):
399
483
"""If the lock is held by this instance, raise an error."""
438
523
info = self._read_info_file(self._held_info_path)
439
524
self._trace("peek -> held")
441
except NoSuchFile, e:
442
527
self._trace("peek -> not held")
444
529
def _prepare_info(self):
445
530
"""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
533
def attempt_lock(self):
472
534
"""Take the lock; fail if it's already held.
531
605
new_info = self.peek()
532
606
if new_info is not None and new_info != last_info:
533
607
if last_info is None:
534
start = 'Unable to obtain'
608
start = gettext('Unable to obtain')
536
start = 'Lock owner changed for'
610
start = gettext('Lock owner changed for')
537
611
last_info = new_info
538
formatted_info = self._format_lock_info(new_info)
612
msg = gettext('{0} lock {1} {2}.').format(start, lock_url,
539
614
if deadline_str is None:
540
615
deadline_str = time.strftime('%H:%M:%S',
541
616
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.',
618
msg += '\n' + gettext(
619
'Will continue to try until %s, unless '
620
'you press Ctrl-C.') % deadline_str
621
msg += '\n' + gettext('See "brz help break-lock" for more.')
622
self._report_function(msg)
560
623
if (max_attempts is not None) and (attempt_count >= max_attempts):
561
624
self._trace("exceeded %d attempts")
562
625
raise LockContention(self)
609
675
# we can't rely on that remotely. Once this is cleaned up,
610
676
# reenable this warning to prevent it coming back in
611
677
# -- mbp 20060303
612
## warn("LockDir.lock_read falls back to write lock")
678
# warn("LockDir.lock_read falls back to write lock")
613
679
if self._lock_held or self._fake_read_lock:
614
680
raise LockContention(self)
615
681
self._fake_read_lock = True
617
def _format_lock_info(self, info):
618
"""Turn the contents of peek() into something for the user"""
619
lock_url = self.transport.abspath(self.path)
620
start_time = info.get('start_time')
621
if start_time is None:
622
time_ago = '(unknown)'
624
time_ago = format_delta(time.time() - int(info['start_time']))
626
'lock %s' % (lock_url,),
627
'held by %s on host %s [process #%s]' %
628
tuple([info.get(x, '<unknown>') for x in ['user', 'hostname', 'pid']]),
629
'locked %s' % (time_ago,),
632
683
def validate_token(self, token):
633
684
if token is not None:
634
685
info = self.peek()
646
697
if 'lock' not in debug.debug_flags:
648
699
mutter(str(self) + ": " + (format % args))
701
def get_config(self):
702
"""Get the configuration that governs this lockdir."""
703
# XXX: This really should also use the locationconfig at least, but
704
# that seems a bit hard to hook up at the moment. -- mbp 20110329
705
# FIXME: The above is still true ;) -- vila 20110811
706
return config.GlobalStack()
709
class LockHeldInfo(object):
710
"""The information recorded about a held lock.
712
This information is recorded into the lock when it's taken, and it can be
713
read back by any process with access to the lockdir. It can be used, for
714
example, to tell the user who holds the lock, or to try to detect whether
715
the lock holder is still alive.
717
Prior to bzr 2.4 a simple dict was used instead of an object.
720
def __init__(self, info_dict):
721
self.info_dict = info_dict
724
"""Return a debugging representation of this object."""
725
return "%s(%r)" % (self.__class__.__name__, self.info_dict)
728
"""Return a user-oriented description of this object."""
729
d = self.to_readable_dict()
731
u'held by %(user)s on %(hostname)s (process #%(pid)s), '
732
u'acquired %(time_ago)s') % d)
734
def to_readable_dict(self):
735
"""Turn the holder info into a dict of human-readable attributes.
737
For example, the start time is presented relative to the current time,
738
rather than as seconds since the epoch.
740
Returns a list of [user, hostname, pid, time_ago] all as readable
743
start_time = self.info_dict.get('start_time')
744
if start_time is None:
745
time_ago = '(unknown)'
747
time_ago = format_delta(
748
time.time() - int(self.info_dict['start_time']))
749
user = self.info_dict.get('user', '<unknown>')
750
hostname = self.info_dict.get('hostname', '<unknown>')
751
pid = self.info_dict.get('pid', '<unknown>')
758
def get(self, field_name):
759
"""Return the contents of a field from the lock info, or None."""
760
return self.info_dict.get(field_name)
763
def for_this_process(cls, extra_holder_info):
764
"""Return a new LockHeldInfo for a lock taken by this process.
767
hostname=get_host_name(),
768
pid=str(os.getpid()),
769
nonce=rand_chars(20).encode('ascii'),
770
start_time=str(int(time.time())),
771
user=get_username_for_lock_info(),
773
if extra_holder_info is not None:
774
info.update(extra_holder_info)
778
s = rio.Stanza(**self.info_dict)
782
def from_info_file_bytes(cls, info_file_bytes):
783
"""Construct from the contents of the held file."""
784
lines = osutils.split_lines(info_file_bytes)
786
stanza = rio.read_stanza(lines)
787
except ValueError as e:
788
mutter('Corrupt lock info file: %r', lines)
789
raise LockCorrupt("could not parse lock info file: " + str(e),
792
# see bug 185013; we fairly often end up with the info file being
793
# empty after an interruption; we could log a message here but
794
# there may not be much we can say
797
ret = stanza.as_dict()
798
ret['nonce'] = ret['nonce'].encode('ascii')
804
def __eq__(self, other):
805
"""Equality check for lock holders."""
806
if type(self) != type(other):
808
return self.info_dict == other.info_dict
810
def __ne__(self, other):
811
return not self == other
813
def is_locked_by_this_process(self):
814
"""True if this process seems to be the current lock holder."""
816
self.get('hostname') == get_host_name()
817
and self.get('pid') == str(os.getpid())
818
and self.get('user') == get_username_for_lock_info())
820
def is_lock_holder_known_dead(self):
821
"""True if the lock holder process is known to be dead.
823
False if it's either known to be still alive, or if we just can't tell.
825
We can be fairly sure the lock holder is dead if it declared the same
826
hostname and there is no process with the given pid alive. If people
827
have multiple machines with the same hostname this may cause trouble.
829
This doesn't check whether the lock holder is in fact the same process
830
calling this method. (In that case it will return true.)
832
if self.get('hostname') != get_host_name():
834
if self.get('hostname') == 'localhost':
837
if self.get('user') != get_username_for_lock_info():
838
# Could well be another local process by a different user, but
839
# just to be safe we won't conclude about this either.
841
pid_str = self.info_dict.get('pid', None)
843
mutter("no pid recorded in %r" % (self, ))
848
mutter("can't parse pid %r from %r"
851
return osutils.is_local_pid_dead(pid)
854
def get_username_for_lock_info():
855
"""Get a username suitable for putting into a lock.
857
It's ok if what's written here is not a proper email address as long
858
as it gives some clue who the user is.
861
return config.GlobalStack().get('email')
862
except errors.NoWhoami:
863
return osutils.getuser_unicode()