1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006, 2007 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.
130
129
from bzrlib.trace import mutter, note
131
from bzrlib.osutils import format_delta, rand_chars, get_host_name
130
from bzrlib.transport import Transport
131
from bzrlib.osutils import rand_chars, format_delta
132
from bzrlib.rio import read_stanza, Stanza
134
from bzrlib.lazy_import import lazy_import
135
lazy_import(globals(), """
136
from bzrlib import rio
139
136
# XXX: At the moment there is no consideration of thread safety on LockDir
140
137
# objects. This should perhaps be updated - e.g. if two threads try to take a
151
148
# files/dirs created.
154
_DEFAULT_TIMEOUT_SECONDS = 30
151
_DEFAULT_TIMEOUT_SECONDS = 300
155
152
_DEFAULT_POLL_SECONDS = 1.0
158
class LockDir(lock.Lock):
159
"""Write-lock guarding access to data.
155
class LockDir(object):
156
"""Write-lock guarding access to data."""
162
158
__INFO_NAME = '/info'
169
165
:param transport: Transport which will contain the lock
171
:param path: Path to the lock within the base directory of the
167
:param path: Path to the lock within the base directory of the
174
170
self.transport = transport
193
189
def create(self, mode=None):
194
190
"""Create the on-disk lock.
196
This is typically only called when the object/directory containing the
192
This is typically only called when the object/directory containing the
197
193
directory is first created. The lock is not held when it's created.
199
195
self._trace("create lock directory")
242
238
# incorrect. It's possible some other servers or filesystems will
243
239
# have a similar bug allowing someone to think they got the lock
244
240
# 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
241
info = self.peek()
251
242
self._trace("after locking, info=%r", info)
253
raise LockFailed(self, "lock was renamed into place, but "
255
if info.get('nonce') != self.nonce:
243
if info['nonce'] != self.nonce:
256
244
self._trace("rename succeeded, "
257
245
"but lock is still held by someone else")
258
246
raise LockContention(self)
264
252
def _remove_pending_dir(self, tmpname):
265
253
"""Remove the pending directory
267
This is called if we failed to rename into place, so that the pending
255
This is called if we failed to rename into place, so that the pending
268
256
dirs don't clutter up the lockdir.
270
258
self._trace("remove %s", tmpname)
304
291
self._fake_read_lock = False
306
293
if not self._lock_held:
307
return lock.cant_unlock_not_held(self)
294
raise LockNotHeld(self)
308
295
if self._locked_via_token:
309
296
self._locked_via_token = False
310
297
self._lock_held = False
312
old_nonce = self.nonce
313
299
# rename before deleting, because we can't atomically remove the
315
301
start_time = time.time()
335
321
self.transport.delete_tree(tmpname)
336
322
self._trace("... unlock succeeded after %dms",
337
323
(time.time() - start_time) * 1000)
338
result = lock.LockResult(self.transport.abspath(self.path),
340
for hook in self.hooks['lock_released']:
343
325
def break_lock(self):
344
326
"""Break a lock not held by this instance of LockDir.
353
335
lock_info = '\n'.join(self._format_lock_info(holder_info))
354
336
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
355
337
self.force_break(holder_info)
357
339
def force_break(self, dead_holder_info):
358
340
"""Release a lock held by another process.
367
349
LockBreakMismatch is raised.
369
351
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
352
It is possible that another process may sneak in and take the
371
353
lock before the breaking process acquires it.
373
355
if not isinstance(dead_holder_info, dict):
382
364
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
383
365
self.transport.rename(self._held_dir, tmpname)
384
366
# check that we actually broke the right lock, not someone else;
385
# there's a small race window between checking it and doing the
367
# there's a small race window between checking it and doing the
387
369
broken_info_path = tmpname + self.__INFO_NAME
388
370
broken_info = self._read_info_file(broken_info_path)
390
372
raise LockBreakMismatch(self, broken_info, dead_holder_info)
391
373
self.transport.delete(broken_info_path)
392
374
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
376
def _check_not_locked(self):
399
377
"""If the lock is held by this instance, raise an error."""
407
385
or if the lock has been affected by a bug.
409
387
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
388
the lock is thought to be held but has been broken, raises
413
391
if not self._lock_held:
419
397
if info.get('nonce') != self.nonce:
420
398
# there is a lock, but not ours
421
399
raise LockBroken(self)
423
401
def _read_info_file(self, path):
424
402
"""Read one given info file.
426
404
peek() reads the info file of the lock holder, if any.
428
return self._parse_info(self.transport.get_bytes(path))
406
return self._parse_info(self.transport.get(path))
431
409
"""Check if the lock is held by anyone.
433
If it is held, this returns the lock info structure as a dict
411
If it is held, this returns the lock info structure as a rio Stanza,
434
412
which contains some information about the current lock holder.
435
413
Otherwise returns None.
444
422
def _prepare_info(self):
445
423
"""Write information about a pending lock to a temporary file.
447
426
# XXX: is creating this here inefficient?
448
427
config = bzrlib.config.GlobalConfig()
429
user = config.user_email()
430
except errors.NoEmailInUsername:
450
431
user = config.username()
451
except errors.NoWhoami:
452
user = osutils.getuser_unicode()
453
s = rio.Stanza(hostname=get_host_name(),
432
s = Stanza(hostname=socket.gethostname(),
454
433
pid=str(os.getpid()),
455
434
start_time=str(int(time.time())),
456
435
nonce=self.nonce,
459
438
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()
440
def _parse_info(self, info_file):
441
return read_stanza(info_file.readlines()).as_dict()
471
443
def attempt_lock(self):
472
444
"""Take the lock; fail if it's already held.
474
446
If you wish to block until the lock can be obtained, call wait_lock()
480
452
if self._fake_read_lock:
481
453
raise LockContention(self)
482
result = self._attempt_lock()
483
hook_result = lock.LockResult(self.transport.abspath(self.path),
485
for hook in self.hooks['lock_acquired']:
454
return self._attempt_lock()
489
456
def wait_lock(self, timeout=None, poll=None, max_attempts=None):
490
457
"""Wait a certain period for a lock.
539
506
if deadline_str is None:
540
507
deadline_str = time.strftime('%H:%M:%S',
541
508
time.localtime(deadline))
542
# As local lock urls are correct we display them.
543
# We avoid displaying remote lock urls.
544
509
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)
510
self._report_function('%s %s\n'
512
'%s\n' # locked ... ago
513
'Will continue to try until %s, unless '
515
'If you\'re sure that it\'s not being '
516
'modified, use bzr break-lock %s',
563
524
if (max_attempts is not None) and (attempt_count >= max_attempts):
564
525
self._trace("exceeded %d attempts")
565
526
raise LockContention(self)
567
528
self._trace("waiting %ss", poll)
570
# As timeout is always 0 for remote locks
571
# this block is applicable only for local
573
531
self._trace("timeout after waiting %ss", timeout)
574
raise LockContention('(local)', lock_url)
532
raise LockContention(self)
576
534
def leave_in_place(self):
577
535
self._locked_via_token = True
582
540
def lock_write(self, token=None):
583
541
"""Wait for and acquire the lock.
585
543
:param token: if this is already locked, then lock_write will fail
586
544
unless the token matches the existing lock.
587
545
:returns: a token if this instance supports tokens, otherwise None.
608
566
def lock_read(self):
609
567
"""Compatibility-mode shared lock.
611
LockDir doesn't support shared read-only locks, so this
569
LockDir doesn't support shared read-only locks, so this
612
570
just pretends that the lock is taken but really does nothing.
614
# At the moment Branches are commonly locked for read, but
572
# At the moment Branches are commonly locked for read, but
615
573
# we can't rely on that remotely. Once this is cleaned up,
616
# reenable this warning to prevent it coming back in
574
# reenable this warning to prevent it coming back in
617
575
# -- mbp 20060303
618
576
## warn("LockDir.lock_read falls back to write lock")
619
577
if self._lock_held or self._fake_read_lock:
623
581
def _format_lock_info(self, info):
624
582
"""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>')
583
lock_url = self.transport.abspath(self.path)
584
delta = time.time() - int(info['start_time'])
586
'lock %s' % (lock_url,),
587
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
588
'locked %s' % (format_delta(delta),),
640
591
def validate_token(self, token):