225
214
:returns: The nonce of the lock, if it was successfully acquired.
227
:raises LockContention: If the lock is held by someone else. The
228
exception contains the info of the current holder of the lock.
216
:raises LockContention: If the lock is held by someone else. The exception
217
contains the info of the current holder of the lock.
230
219
self._trace("lock_write...")
231
220
start_time = time.time()
233
222
tmpname = self._create_pending_dir()
234
except (errors.TransportError, PathError) as e:
223
except (errors.TransportError, PathError), e:
235
224
self._trace("... failed to create pending dir, %s", e)
236
225
raise LockFailed(self, e)
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)
227
self.transport.rename(tmpname, self._held_dir)
228
except (errors.TransportError, PathError, DirectoryNotEmpty,
229
FileExists, ResourceBusy), e:
230
self._trace("... contention, %s", e)
231
self._remove_pending_dir(tmpname)
232
raise LockContention(self)
234
self._trace("... lock failed, %s", e)
235
self._remove_pending_dir(tmpname)
255
237
# We must check we really got the lock, because Launchpad's sftp
256
238
# server at one time had a bug were the rename would successfully
257
239
# move the new directory into the existing directory, which was
267
249
self._trace("after locking, info=%r", info)
269
251
raise LockFailed(self, "lock was renamed into place, but "
271
if info.get('nonce') != self.nonce:
253
if info['nonce'] != self.nonce:
272
254
self._trace("rename succeeded, "
273
"but lock is still held by someone else")
255
"but lock is still held by someone else")
274
256
raise LockContention(self)
275
257
self._lock_held = True
276
258
self._trace("... lock succeeded after %dms",
277
(time.time() - start_time) * 1000)
259
(time.time() - start_time) * 1000)
278
260
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)
305
262
def _remove_pending_dir(self, tmpname):
306
263
"""Remove the pending directory
363
319
self.transport.delete(tmpname + self.__INFO_NAME)
365
321
self.transport.rmdir(tmpname)
366
except DirectoryNotEmpty:
322
except DirectoryNotEmpty, e:
367
323
# There might have been junk left over by a rename that moved
368
324
# another locker within the 'held' directory. do a slower
369
325
# deletion where we list the directory and remove everything
328
# Maybe this should be broader to allow for ftp servers with
329
# non-specific error messages?
371
330
self._trace("doing recursive deletion of non-empty directory "
373
332
self.transport.delete_tree(tmpname)
374
333
self._trace("... unlock succeeded after %dms",
375
(time.time() - start_time) * 1000)
334
(time.time() - start_time) * 1000)
376
335
result = lock.LockResult(self.transport.abspath(self.path),
378
337
for hook in self.hooks['lock_released']:
381
340
def break_lock(self):
382
341
"""Break a lock not held by this instance of LockDir.
384
This is a UI centric function: it uses the ui.ui_factory to
343
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
385
344
prompt for input if a lock is detected and there is any doubt about
386
it possibly being still active. force_break is the non-interactive
389
:returns: LockResult for the broken lock.
345
it possibly being still active.
391
347
self._check_not_locked()
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)
348
holder_info = self.peek()
399
349
if holder_info is not None:
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)
350
lock_info = '\n'.join(self._format_lock_info(holder_info))
351
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
352
self.force_break(holder_info)
408
354
def force_break(self, dead_holder_info):
409
355
"""Release a lock held by another process.
412
358
it still thinks it has the lock there will be two concurrent writers.
413
359
In general the user's approval should be sought for lock breaks.
361
dead_holder_info must be the result of a previous LockDir.peek() call;
362
this is used to check that it's still held by the same process that
363
the user decided was dead. If this is not the current holder,
364
LockBreakMismatch is raised.
415
366
After the lock is broken it will not be held by any process.
416
367
It is possible that another process may sneak in and take the
417
368
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.
427
if not isinstance(dead_holder_info, LockHeldInfo):
370
if not isinstance(dead_holder_info, dict):
428
371
raise ValueError("dead_holder_info: %r" % dead_holder_info)
429
372
self._check_not_locked()
430
373
current_info = self.peek()
448
391
current_info.get('nonce'))
449
392
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']:
482
395
def _check_not_locked(self):
483
396
"""If the lock is held by this instance, raise an error."""
605
521
new_info = self.peek()
606
522
if new_info is not None and new_info != last_info:
607
523
if last_info is None:
608
start = gettext('Unable to obtain')
524
start = 'Unable to obtain'
610
start = gettext('Lock owner changed for')
526
start = 'Lock owner changed for'
611
527
last_info = new_info
612
msg = gettext('{0} lock {1} {2}.').format(start, lock_url,
528
formatted_info = self._format_lock_info(new_info)
614
529
if deadline_str is None:
615
530
deadline_str = time.strftime('%H:%M:%S',
616
531
time.localtime(deadline))
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)
532
lock_url = self.transport.abspath(self.path)
533
self._report_function('%s %s\n'
535
'%s\n' # locked ... ago
536
'Will continue to try until %s, unless '
538
'If you\'re sure that it\'s not being '
539
'modified, use bzr break-lock %s',
623
547
if (max_attempts is not None) and (attempt_count >= max_attempts):
624
548
self._trace("exceeded %d attempts")
625
549
raise LockContention(self)
675
596
# we can't rely on that remotely. Once this is cleaned up,
676
597
# reenable this warning to prevent it coming back in
677
598
# -- mbp 20060303
678
# warn("LockDir.lock_read falls back to write lock")
599
## warn("LockDir.lock_read falls back to write lock")
679
600
if self._lock_held or self._fake_read_lock:
680
601
raise LockContention(self)
681
602
self._fake_read_lock = True
604
def _format_lock_info(self, info):
605
"""Turn the contents of peek() into something for the user"""
606
lock_url = self.transport.abspath(self.path)
607
delta = time.time() - int(info['start_time'])
609
'lock %s' % (lock_url,),
610
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
611
'locked %s' % (format_delta(delta),),
683
614
def validate_token(self, token):
684
615
if token is not None:
685
616
info = self.peek()
697
628
if 'lock' not in debug.debug_flags:
699
630
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()