225
217
: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.
219
:raises LockContention: If the lock is held by someone else. The exception
220
contains the info of the current holder of the lock.
230
222
self._trace("lock_write...")
231
223
start_time = time.time()
233
225
tmpname = self._create_pending_dir()
234
except (errors.TransportError, PathError) as e:
226
except (errors.TransportError, PathError), e:
235
227
self._trace("... failed to create pending dir, %s", e)
236
228
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)
230
self.transport.rename(tmpname, self._held_dir)
231
except (errors.TransportError, PathError, DirectoryNotEmpty,
232
FileExists, ResourceBusy), e:
233
self._trace("... contention, %s", e)
234
self._remove_pending_dir(tmpname)
235
raise LockContention(self)
237
self._trace("... lock failed, %s", e)
238
self._remove_pending_dir(tmpname)
255
240
# We must check we really got the lock, because Launchpad's sftp
256
241
# server at one time had a bug were the rename would successfully
257
242
# move the new directory into the existing directory, which was
267
252
self._trace("after locking, info=%r", info)
269
254
raise LockFailed(self, "lock was renamed into place, but "
271
256
if info.get('nonce') != self.nonce:
272
257
self._trace("rename succeeded, "
273
"but lock is still held by someone else")
258
"but lock is still held by someone else")
274
259
raise LockContention(self)
275
260
self._lock_held = True
276
261
self._trace("... lock succeeded after %dms",
277
(time.time() - start_time) * 1000)
262
(time.time() - start_time) * 1000)
278
263
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
265
def _remove_pending_dir(self, tmpname):
306
266
"""Remove the pending directory
363
323
self.transport.delete(tmpname + self.__INFO_NAME)
365
325
self.transport.rmdir(tmpname)
366
except DirectoryNotEmpty:
326
except DirectoryNotEmpty, e:
367
327
# There might have been junk left over by a rename that moved
368
328
# another locker within the 'held' directory. do a slower
369
329
# deletion where we list the directory and remove everything
332
# Maybe this should be broader to allow for ftp servers with
333
# non-specific error messages?
371
334
self._trace("doing recursive deletion of non-empty directory "
373
336
self.transport.delete_tree(tmpname)
374
337
self._trace("... unlock succeeded after %dms",
375
(time.time() - start_time) * 1000)
338
(time.time() - start_time) * 1000)
376
339
result = lock.LockResult(self.transport.abspath(self.path),
378
341
for hook in self.hooks['lock_released']:
381
344
def break_lock(self):
382
345
"""Break a lock not held by this instance of LockDir.
384
This is a UI centric function: it uses the ui.ui_factory to
347
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
385
348
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
349
it possibly being still active.
389
351
:returns: LockResult for the broken lock.
391
353
self._check_not_locked()
393
355
holder_info = self.peek()
394
except LockCorrupt as e:
356
except LockCorrupt, e:
395
357
# The lock info is corrupt.
396
if ui.ui_factory.get_boolean(u"Break (corrupt %r)" % (self,)):
358
if bzrlib.ui.ui_factory.get_boolean("Break (corrupt %r)" % (self,)):
397
359
self.force_break_corrupt(e.file_data)
399
361
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))):
362
lock_info = '\n'.join(self._format_lock_info(holder_info))
363
if bzrlib.ui.ui_factory.confirm_action(
364
"Break %(lock_info)s", 'bzrlib.lockdir.break',
365
dict(lock_info=lock_info)):
404
366
result = self.force_break(holder_info)
405
ui.ui_factory.show_message(
367
bzrlib.ui.ui_factory.show_message(
406
368
"Broke lock %s" % result.lock_url)
408
370
def force_break(self, dead_holder_info):
412
374
it still thinks it has the lock there will be two concurrent writers.
413
375
In general the user's approval should be sought for lock breaks.
377
dead_holder_info must be the result of a previous LockDir.peek() call;
378
this is used to check that it's still held by the same process that
379
the user decided was dead. If this is not the current holder,
380
LockBreakMismatch is raised.
415
382
After the lock is broken it will not be held by any process.
416
383
It is possible that another process may sneak in and take the
417
384
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
386
:returns: LockResult for the broken lock.
427
if not isinstance(dead_holder_info, LockHeldInfo):
388
if not isinstance(dead_holder_info, dict):
428
389
raise ValueError("dead_holder_info: %r" % dead_holder_info)
429
390
self._check_not_locked()
430
391
current_info = self.peek()
523
483
info = self._read_info_file(self._held_info_path)
524
484
self._trace("peek -> held")
486
except NoSuchFile, e:
527
487
self._trace("peek -> not held")
529
489
def _prepare_info(self):
530
490
"""Write information about a pending lock to a temporary file.
492
# XXX: is creating this here inefficient?
493
config = bzrlib.config.GlobalConfig()
495
user = config.username()
496
except errors.NoWhoami:
497
user = osutils.getuser_unicode()
498
s = rio.Stanza(hostname=get_host_name(),
499
pid=str(os.getpid()),
500
start_time=str(int(time.time())),
506
def _parse_info(self, info_bytes):
507
lines = osutils.split_lines(info_bytes)
509
stanza = rio.read_stanza(lines)
510
except ValueError, e:
511
mutter('Corrupt lock info file: %r', lines)
512
raise LockCorrupt("could not parse lock info file: " + str(e),
515
# see bug 185013; we fairly often end up with the info file being
516
# empty after an interruption; we could log a message here but
517
# there may not be much we can say
520
return stanza.as_dict()
533
522
def attempt_lock(self):
534
523
"""Take the lock; fail if it's already held.
605
594
new_info = self.peek()
606
595
if new_info is not None and new_info != last_info:
607
596
if last_info is None:
608
start = gettext('Unable to obtain')
597
start = 'Unable to obtain'
610
start = gettext('Lock owner changed for')
599
start = 'Lock owner changed for'
611
600
last_info = new_info
612
msg = gettext('{0} lock {1} {2}.').format(start, lock_url,
601
formatted_info = self._format_lock_info(new_info)
614
602
if deadline_str is None:
615
603
deadline_str = time.strftime('%H:%M:%S',
616
604
time.localtime(deadline))
605
user, hostname, pid, time_ago = formatted_info
606
msg = ('%s lock %s ' # lock_url
610
'[process #%s], ' # pid
611
'acquired %s.') # time ago
612
msg_args = [start, lock_url, user, hostname, pid, time_ago]
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)
614
msg += ('\nWill continue to try until %s, unless '
616
msg_args.append(deadline_str)
617
msg += '\nSee "bzr help break-lock" for more.'
618
self._report_function(msg, *msg_args)
623
619
if (max_attempts is not None) and (attempt_count >= max_attempts):
624
620
self._trace("exceeded %d attempts")
625
621
raise LockContention(self)
675
671
# we can't rely on that remotely. Once this is cleaned up,
676
672
# reenable this warning to prevent it coming back in
677
673
# -- mbp 20060303
678
# warn("LockDir.lock_read falls back to write lock")
674
## warn("LockDir.lock_read falls back to write lock")
679
675
if self._lock_held or self._fake_read_lock:
680
676
raise LockContention(self)
681
677
self._fake_read_lock = True
679
def _format_lock_info(self, info):
680
"""Turn the contents of peek() into something for the user"""
681
start_time = info.get('start_time')
682
if start_time is None:
683
time_ago = '(unknown)'
685
time_ago = format_delta(time.time() - int(info['start_time']))
686
user = info.get('user', '<unknown>')
687
hostname = info.get('hostname', '<unknown>')
688
pid = info.get('pid', '<unknown>')
683
696
def validate_token(self, token):
684
697
if token is not None:
685
698
info = self.peek()
697
710
if 'lock' not in debug.debug_flags:
699
712
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()