1
# Copyright (C) 2005-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
18
"""Locking using OS file locks or file existence.
20
Note: This method of locking is generally deprecated in favour of LockDir, but
21
is used to lock local WorkingTrees, and by some old formats. It's accessed
22
through Transport.lock_read(), etc.
24
This module causes two methods, lock() and unlock() to be defined in
25
any way that works on the current platform.
27
It is not specified whether these locks are reentrant (i.e. can be
28
taken repeatedly by a single process) or whether they exclude
29
different threads in a single process. That reentrancy is provided by
32
This defines two classes: ReadLock and WriteLock, which can be
33
implemented in different ways on different platforms. Both have an
48
from bzrlib.hooks import HookPoint, Hooks
51
class LockHooks(Hooks):
55
self.create_hook(HookPoint('lock_acquired',
56
"Called with a bzrlib.lock.LockResult when a physical lock is "
57
"acquired.", (1, 8), None))
58
self.create_hook(HookPoint('lock_released',
59
"Called with a bzrlib.lock.LockResult when a physical lock is "
60
"released.", (1, 8), None))
61
self.create_hook(HookPoint('lock_broken',
62
"Called with a bzrlib.lock.LockResult when a physical lock is "
63
"broken.", (1, 15), None))
67
"""Base class for locks.
69
:cvar hooks: Hook dictionary for operations on locks.
75
class LockResult(object):
76
"""Result of an operation on a lock; passed to a hook"""
78
def __init__(self, lock_url, details=None):
79
"""Create a lock result for lock with optional details about the lock."""
80
self.lock_url = lock_url
81
self.details = details
83
def __eq__(self, other):
84
return self.lock_url == other.lock_url and self.details == other.details
87
return '%s(%s, %s)' % (self.__class__.__name__,
88
self.lock_url, self.details)
91
class LogicalLockResult(object):
92
"""The result of a lock_read/lock_write/lock_tree_write call on lockables.
94
:ivar unlock: A callable which will unlock the lock.
97
def __init__(self, unlock):
101
return "LogicalLockResult(%s)" % (self.unlock)
105
def cant_unlock_not_held(locked_object):
106
"""An attempt to unlock failed because the object was not locked.
108
This provides a policy point from which we can generate either a warning
111
# This is typically masking some other error and called from a finally
112
# block, so it's useful to have the option not to generate a new error
113
# here. You can use -Werror to make it fatal. It should possibly also
115
if 'unlock' in debug.debug_flags:
116
warnings.warn("%r is already unlocked" % (locked_object,),
119
raise errors.LockNotHeld(locked_object)
129
have_ctypes_win32 = False
130
if sys.platform == 'win32':
133
import win32file, pywintypes, winerror
140
have_ctypes_win32 = True
145
class _OSLock(object):
151
def _open(self, filename, filemode):
152
self.filename = osutils.realpath(filename)
154
self.f = open(self.filename, filemode)
157
if e.errno in (errno.EACCES, errno.EPERM):
158
raise errors.LockFailed(self.filename, str(e))
159
if e.errno != errno.ENOENT:
162
# maybe this is an old branch (before may 2005)
163
trace.mutter("trying to create missing lock %r", self.filename)
165
self.f = open(self.filename, 'wb+')
169
"""Clear the self.f attribute cleanly."""
176
from warnings import warn
177
warn("lock on %r not released" % self.f)
181
raise NotImplementedError()
189
class _fcntl_FileLock(_OSLock):
192
fcntl.lockf(self.f, fcntl.LOCK_UN)
196
class _fcntl_WriteLock(_fcntl_FileLock):
200
def __init__(self, filename):
201
super(_fcntl_WriteLock, self).__init__()
202
# Check we can grab a lock before we actually open the file.
203
self.filename = osutils.realpath(filename)
204
if self.filename in _fcntl_WriteLock._open_locks:
206
raise errors.LockContention(self.filename)
207
if self.filename in _fcntl_ReadLock._open_locks:
208
if 'strict_locks' in debug.debug_flags:
210
raise errors.LockContention(self.filename)
212
trace.mutter('Write lock taken w/ an open read lock on: %s'
215
self._open(self.filename, 'rb+')
216
# reserve a slot for this lock - even if the lockf call fails,
217
# at this point unlock() will be called, because self.f is set.
218
# TODO: make this fully threadsafe, if we decide we care.
219
_fcntl_WriteLock._open_locks.add(self.filename)
221
# LOCK_NB will cause IOError to be raised if we can't grab a
223
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
225
if e.errno in (errno.EAGAIN, errno.EACCES):
226
# We couldn't grab the lock
228
# we should be more precise about whats a locking
229
# error and whats a random-other error
230
raise errors.LockContention(self.filename, e)
233
_fcntl_WriteLock._open_locks.remove(self.filename)
237
class _fcntl_ReadLock(_fcntl_FileLock):
241
def __init__(self, filename):
242
super(_fcntl_ReadLock, self).__init__()
243
self.filename = osutils.realpath(filename)
244
if self.filename in _fcntl_WriteLock._open_locks:
245
if 'strict_locks' in debug.debug_flags:
246
# We raise before calling _open so we don't need to
248
raise errors.LockContention(self.filename)
250
trace.mutter('Read lock taken w/ an open write lock on: %s'
252
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
253
_fcntl_ReadLock._open_locks[self.filename] += 1
254
self._open(filename, 'rb')
256
# LOCK_NB will cause IOError to be raised if we can't grab a
258
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
260
# we should be more precise about whats a locking
261
# error and whats a random-other error
262
raise errors.LockContention(self.filename, e)
265
count = _fcntl_ReadLock._open_locks[self.filename]
267
del _fcntl_ReadLock._open_locks[self.filename]
269
_fcntl_ReadLock._open_locks[self.filename] = count - 1
272
def temporary_write_lock(self):
273
"""Try to grab a write lock on the file.
275
On platforms that support it, this will upgrade to a write lock
276
without unlocking the file.
277
Otherwise, this will release the read lock, and try to acquire a
280
:return: A token which can be used to switch back to a read lock.
282
if self.filename in _fcntl_WriteLock._open_locks:
283
raise AssertionError('file already locked: %r'
286
wlock = _fcntl_TemporaryWriteLock(self)
287
except errors.LockError:
288
# We didn't unlock, so we can just return 'self'
293
class _fcntl_TemporaryWriteLock(_OSLock):
294
"""A token used when grabbing a temporary_write_lock.
296
Call restore_read_lock() when you are done with the write lock.
299
def __init__(self, read_lock):
300
super(_fcntl_TemporaryWriteLock, self).__init__()
301
self._read_lock = read_lock
302
self.filename = read_lock.filename
304
count = _fcntl_ReadLock._open_locks[self.filename]
306
# Something else also has a read-lock, so we cannot grab a
308
raise errors.LockContention(self.filename)
310
if self.filename in _fcntl_WriteLock._open_locks:
311
raise AssertionError('file already locked: %r'
314
# See if we can open the file for writing. Another process might
315
# have a read lock. We don't use self._open() because we don't want
316
# to create the file if it exists. That would have already been
317
# done by _fcntl_ReadLock
319
new_f = open(self.filename, 'rb+')
321
if e.errno in (errno.EACCES, errno.EPERM):
322
raise errors.LockFailed(self.filename, str(e))
325
# LOCK_NB will cause IOError to be raised if we can't grab a
327
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
329
# TODO: Raise a more specific error based on the type of error
330
raise errors.LockContention(self.filename, e)
331
_fcntl_WriteLock._open_locks.add(self.filename)
335
def restore_read_lock(self):
336
"""Restore the original ReadLock."""
337
# For fcntl, since we never released the read lock, just release the
338
# write lock, and return the original lock.
339
fcntl.lockf(self.f, fcntl.LOCK_UN)
341
_fcntl_WriteLock._open_locks.remove(self.filename)
342
# Avoid reference cycles
343
read_lock = self._read_lock
344
self._read_lock = None
348
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
351
if have_pywin32 and sys.platform == 'win32':
352
if os.path.supports_unicode_filenames:
353
# for Windows NT/2K/XP/etc
354
win32file_CreateFile = win32file.CreateFileW
357
win32file_CreateFile = win32file.CreateFile
359
class _w32c_FileLock(_OSLock):
361
def _open(self, filename, access, share, cflags, pymode):
362
self.filename = osutils.realpath(filename)
364
self._handle = win32file_CreateFile(filename, access, share,
365
None, win32file.OPEN_ALWAYS,
366
win32file.FILE_ATTRIBUTE_NORMAL, None)
367
except pywintypes.error, e:
368
if e.args[0] == winerror.ERROR_ACCESS_DENIED:
369
raise errors.LockFailed(filename, e)
370
if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
371
raise errors.LockContention(filename, e)
373
fd = win32file._open_osfhandle(self._handle, cflags)
374
self.f = os.fdopen(fd, pymode)
382
class _w32c_ReadLock(_w32c_FileLock):
383
def __init__(self, filename):
384
super(_w32c_ReadLock, self).__init__()
385
self._open(filename, win32file.GENERIC_READ,
386
win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
388
def temporary_write_lock(self):
389
"""Try to grab a write lock on the file.
391
On platforms that support it, this will upgrade to a write lock
392
without unlocking the file.
393
Otherwise, this will release the read lock, and try to acquire a
396
:return: A token which can be used to switch back to a read lock.
398
# I can't find a way to upgrade a read lock to a write lock without
399
# unlocking first. So here, we do just that.
402
wlock = _w32c_WriteLock(self.filename)
403
except errors.LockError:
404
return False, _w32c_ReadLock(self.filename)
408
class _w32c_WriteLock(_w32c_FileLock):
409
def __init__(self, filename):
410
super(_w32c_WriteLock, self).__init__()
412
win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
415
def restore_read_lock(self):
416
"""Restore the original ReadLock."""
417
# For win32 we had to completely let go of the original lock, so we
418
# just unlock and create a new read lock.
420
return _w32c_ReadLock(self.filename)
423
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
426
if have_ctypes_win32:
427
from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
428
LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
429
HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
430
if os.path.supports_unicode_filenames:
431
_function_name = "CreateFileW"
434
_function_name = "CreateFileA"
435
class LPTSTR(LPCSTR):
436
def __new__(cls, obj):
437
return LPCSTR.__new__(cls, obj.encode("mbcs"))
439
# CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
440
_CreateFile = ctypes.WINFUNCTYPE(
441
HANDLE, # return value
443
DWORD, # dwDesiredAccess
445
LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
446
DWORD, # dwCreationDisposition
447
DWORD, # dwFlagsAndAttributes
448
HANDLE # hTemplateFile
449
)((_function_name, ctypes.windll.kernel32))
451
INVALID_HANDLE_VALUE = -1
453
GENERIC_READ = 0x80000000
454
GENERIC_WRITE = 0x40000000
457
FILE_ATTRIBUTE_NORMAL = 128
459
ERROR_ACCESS_DENIED = 5
460
ERROR_SHARING_VIOLATION = 32
462
class _ctypes_FileLock(_OSLock):
464
def _open(self, filename, access, share, cflags, pymode):
465
self.filename = osutils.realpath(filename)
466
handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
467
FILE_ATTRIBUTE_NORMAL, 0)
468
if handle in (INVALID_HANDLE_VALUE, 0):
469
e = ctypes.WinError()
470
if e.args[0] == ERROR_ACCESS_DENIED:
471
raise errors.LockFailed(filename, e)
472
if e.args[0] == ERROR_SHARING_VIOLATION:
473
raise errors.LockContention(filename, e)
475
fd = msvcrt.open_osfhandle(handle, cflags)
476
self.f = os.fdopen(fd, pymode)
483
class _ctypes_ReadLock(_ctypes_FileLock):
484
def __init__(self, filename):
485
super(_ctypes_ReadLock, self).__init__()
486
self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
489
def temporary_write_lock(self):
490
"""Try to grab a write lock on the file.
492
On platforms that support it, this will upgrade to a write lock
493
without unlocking the file.
494
Otherwise, this will release the read lock, and try to acquire a
497
:return: A token which can be used to switch back to a read lock.
499
# I can't find a way to upgrade a read lock to a write lock without
500
# unlocking first. So here, we do just that.
503
wlock = _ctypes_WriteLock(self.filename)
504
except errors.LockError:
505
return False, _ctypes_ReadLock(self.filename)
508
class _ctypes_WriteLock(_ctypes_FileLock):
509
def __init__(self, filename):
510
super(_ctypes_WriteLock, self).__init__()
511
self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
514
def restore_read_lock(self):
515
"""Restore the original ReadLock."""
516
# For win32 we had to completely let go of the original lock, so we
517
# just unlock and create a new read lock.
519
return _ctypes_ReadLock(self.filename)
522
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
525
if len(_lock_classes) == 0:
526
raise NotImplementedError(
527
"We must have one of fcntl, pywin32, or ctypes available"
528
" to support OS locking."
532
# We default to using the first available lock class.
533
_lock_type, WriteLock, ReadLock = _lock_classes[0]
536
class _RelockDebugMixin(object):
537
"""Mixin support for -Drelock flag.
539
Add this as a base class then call self._note_lock with 'r' or 'w' when
540
acquiring a read- or write-lock. If this object was previously locked (and
541
locked the same way), and -Drelock is set, then this will trace.note a
547
def _note_lock(self, lock_type):
548
if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
553
trace.note('%r was %s locked again', self, type_name)
554
self._prev_lock = lock_type