1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""On-disk mutex protecting a resource
21
21
internal locks (such as flock etc) because they can be seen across all
22
22
transports, including http.
24
Objects can be read if there is only physical read access; therefore
24
Objects can be read if there is only physical read access; therefore
25
25
readers can never be required to create a lock, though they will
26
26
check whether a writer is using the lock. Writers can't detect
27
27
whether anyone else is reading from the resource as they write.
57
57
The desired characteristics are:
59
* Locks are not reentrant. (That is, a client that tries to take a
59
* Locks are not reentrant. (That is, a client that tries to take a
60
60
lock it already holds may deadlock or fail.)
61
61
* Stale locks can be guessed at by a heuristic
62
62
* Lost locks can be broken by any client
78
78
and deadlocks will likely occur if the locks are aliased.
80
80
In the future we may add a "freshen" method which can be called
81
by a lock holder to check that their lock has not been broken, and to
81
by a lock holder to check that their lock has not been broken, and to
82
82
update the timestamp within it.
128
from bzrlib.hooks import Hooks
130
129
from bzrlib.trace import mutter, note
131
130
from bzrlib.osutils import format_delta, rand_chars, get_host_name
169
168
:param transport: Transport which will contain the lock
171
:param path: Path to the lock within the base directory of the
170
:param path: Path to the lock within the base directory of the
174
173
self.transport = transport
193
192
def create(self, mode=None):
194
193
"""Create the on-disk lock.
196
This is typically only called when the object/directory containing the
195
This is typically only called when the object/directory containing the
197
196
directory is first created. The lock is not held when it's created.
199
198
self._trace("create lock directory")
242
241
# incorrect. It's possible some other servers or filesystems will
243
242
# have a similar bug allowing someone to think they got the lock
244
243
# when it's already held.
246
# See <https://bugs.launchpad.net/bzr/+bug/498378> for one case.
248
# Strictly the check is unnecessary and a waste of time for most
249
# people, but probably worth trapping if something is wrong.
250
244
info = self.peek()
251
245
self._trace("after locking, info=%r", info)
253
raise LockFailed(self, "lock was renamed into place, but "
255
if info.get('nonce') != self.nonce:
246
if info['nonce'] != self.nonce:
256
247
self._trace("rename succeeded, "
257
248
"but lock is still held by someone else")
258
249
raise LockContention(self)
264
255
def _remove_pending_dir(self, tmpname):
265
256
"""Remove the pending directory
267
This is called if we failed to rename into place, so that the pending
258
This is called if we failed to rename into place, so that the pending
268
259
dirs don't clutter up the lockdir.
270
261
self._trace("remove %s", tmpname)
336
326
self._trace("... unlock succeeded after %dms",
337
327
(time.time() - start_time) * 1000)
338
328
result = lock.LockResult(self.transport.abspath(self.path),
340
330
for hook in self.hooks['lock_released']:
353
343
lock_info = '\n'.join(self._format_lock_info(holder_info))
354
344
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
355
345
self.force_break(holder_info)
357
347
def force_break(self, dead_holder_info):
358
348
"""Release a lock held by another process.
367
357
LockBreakMismatch is raised.
369
359
After the lock is broken it will not be held by any process.
370
It is possible that another process may sneak in and take the
360
It is possible that another process may sneak in and take the
371
361
lock before the breaking process acquires it.
373
363
if not isinstance(dead_holder_info, dict):
382
372
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
383
373
self.transport.rename(self._held_dir, tmpname)
384
374
# check that we actually broke the right lock, not someone else;
385
# there's a small race window between checking it and doing the
375
# there's a small race window between checking it and doing the
387
377
broken_info_path = tmpname + self.__INFO_NAME
388
378
broken_info = self._read_info_file(broken_info_path)
390
380
raise LockBreakMismatch(self, broken_info, dead_holder_info)
391
381
self.transport.delete(broken_info_path)
392
382
self.transport.rmdir(tmpname)
393
result = lock.LockResult(self.transport.abspath(self.path),
394
current_info.get('nonce'))
395
for hook in self.hooks['lock_broken']:
398
384
def _check_not_locked(self):
399
385
"""If the lock is held by this instance, raise an error."""
407
393
or if the lock has been affected by a bug.
409
395
If the lock is not thought to be held, raises LockNotHeld. If
410
the lock is thought to be held but has been broken, raises
396
the lock is thought to be held but has been broken, raises
413
399
if not self._lock_held:
419
405
if info.get('nonce') != self.nonce:
420
406
# there is a lock, but not ours
421
407
raise LockBroken(self)
423
409
def _read_info_file(self, path):
424
410
"""Read one given info file.
426
412
peek() reads the info file of the lock holder, if any.
428
return self._parse_info(self.transport.get_bytes(path))
414
return self._parse_info(self.transport.get(path))
431
417
"""Check if the lock is held by anyone.
433
If it is held, this returns the lock info structure as a dict
419
If it is held, this returns the lock info structure as a rio Stanza,
434
420
which contains some information about the current lock holder.
435
421
Otherwise returns None.
447
433
# XXX: is creating this here inefficient?
448
434
config = bzrlib.config.GlobalConfig()
436
user = config.user_email()
437
except errors.NoEmailInUsername:
450
438
user = config.username()
451
except errors.NoWhoami:
452
user = osutils.getuser_unicode()
453
439
s = rio.Stanza(hostname=get_host_name(),
454
440
pid=str(os.getpid()),
455
441
start_time=str(int(time.time())),
459
445
return s.to_string()
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()
447
def _parse_info(self, info_file):
448
return rio.read_stanza(info_file.readlines()).as_dict()
471
450
def attempt_lock(self):
472
451
"""Take the lock; fail if it's already held.
474
453
If you wish to block until the lock can be obtained, call wait_lock()
539
518
if deadline_str is None:
540
519
deadline_str = time.strftime('%H:%M:%S',
541
520
time.localtime(deadline))
542
# As local lock urls are correct we display them.
543
# We avoid displaying remote lock urls.
544
521
lock_url = self.transport.abspath(self.path)
545
if lock_url.startswith('file://'):
546
lock_url = lock_url.split('.bzr/')[0]
549
user, hostname, pid, time_ago = formatted_info
550
msg = ('%s lock %s ' # lock_url
554
'[process #%s], ' # pid
555
'acquired %s.') # time ago
556
msg_args = [start, lock_url, user, hostname, pid, time_ago]
558
msg += ('\nWill continue to try until %s, unless '
560
msg_args.append(deadline_str)
561
msg += '\nSee "bzr help break-lock" for more.'
562
self._report_function(msg, *msg_args)
522
self._report_function('%s %s\n'
524
'%s\n' # locked ... ago
525
'Will continue to try until %s, unless '
527
'If you\'re sure that it\'s not being '
528
'modified, use bzr break-lock %s',
563
536
if (max_attempts is not None) and (attempt_count >= max_attempts):
564
537
self._trace("exceeded %d attempts")
565
538
raise LockContention(self)
567
540
self._trace("waiting %ss", poll)
570
# As timeout is always 0 for remote locks
571
# this block is applicable only for local
573
543
self._trace("timeout after waiting %ss", timeout)
574
raise LockContention('(local)', lock_url)
544
raise LockContention(self)
576
546
def leave_in_place(self):
577
547
self._locked_via_token = True
582
552
def lock_write(self, token=None):
583
553
"""Wait for and acquire the lock.
585
555
:param token: if this is already locked, then lock_write will fail
586
556
unless the token matches the existing lock.
587
557
:returns: a token if this instance supports tokens, otherwise None.
608
578
def lock_read(self):
609
579
"""Compatibility-mode shared lock.
611
LockDir doesn't support shared read-only locks, so this
581
LockDir doesn't support shared read-only locks, so this
612
582
just pretends that the lock is taken but really does nothing.
614
# At the moment Branches are commonly locked for read, but
584
# At the moment Branches are commonly locked for read, but
615
585
# we can't rely on that remotely. Once this is cleaned up,
616
# reenable this warning to prevent it coming back in
586
# reenable this warning to prevent it coming back in
617
587
# -- mbp 20060303
618
588
## warn("LockDir.lock_read falls back to write lock")
619
589
if self._lock_held or self._fake_read_lock:
623
593
def _format_lock_info(self, info):
624
594
"""Turn the contents of peek() into something for the user"""
625
start_time = info.get('start_time')
626
if start_time is None:
627
time_ago = '(unknown)'
629
time_ago = format_delta(time.time() - int(info['start_time']))
630
user = info.get('user', '<unknown>')
631
hostname = info.get('hostname', '<unknown>')
632
pid = info.get('pid', '<unknown>')
595
lock_url = self.transport.abspath(self.path)
596
delta = time.time() - int(info['start_time'])
598
'lock %s' % (lock_url,),
599
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
600
'locked %s' % (format_delta(delta),),
640
603
def validate_token(self, token):