36
from __future__ import absolute_import
50
from bzrlib.hooks import Hooks
51
from bzrlib.i18n import gettext
53
class LockHooks(Hooks):
56
Hooks.__init__(self, "bzrlib.lock", "Lock.hooks")
57
self.add_hook('lock_acquired',
58
"Called with a bzrlib.lock.LockResult when a physical lock is "
60
self.add_hook('lock_released',
61
"Called with a bzrlib.lock.LockResult when a physical lock is "
63
self.add_hook('lock_broken',
64
"Called with a bzrlib.lock.LockResult when a physical lock is "
69
"""Base class for locks.
71
:cvar hooks: Hook dictionary for operations on locks.
77
class LockResult(object):
78
"""Result of an operation on a lock; passed to a hook"""
80
def __init__(self, lock_url, details=None):
81
"""Create a lock result for lock with optional details about the lock."""
82
self.lock_url = lock_url
83
self.details = details
85
def __eq__(self, other):
86
return self.lock_url == other.lock_url and self.details == other.details
89
return '%s(%s, %s)' % (self.__class__.__name__,
90
self.lock_url, self.details)
93
class LogicalLockResult(object):
94
"""The result of a lock_read/lock_write/lock_tree_write call on lockables.
96
:ivar unlock: A callable which will unlock the lock.
99
def __init__(self, unlock):
103
return "LogicalLockResult(%s)" % (self.unlock)
107
def cant_unlock_not_held(locked_object):
108
"""An attempt to unlock failed because the object was not locked.
110
This provides a policy point from which we can generate either a warning
113
# This is typically masking some other error and called from a finally
114
# block, so it's useful to have the option not to generate a new error
115
# here. You can use -Werror to make it fatal. It should possibly also
117
if 'unlock' in debug.debug_flags:
118
warnings.warn("%r is already unlocked" % (locked_object,),
121
raise errors.LockNotHeld(locked_object)
131
have_ctypes_win32 = False
132
if sys.platform == 'win32':
135
import win32file, pywintypes, winerror
142
have_ctypes_win32 = True
147
class _OSLock(object):
41
from bzrlib.errors import LockError
42
from bzrlib.osutils import realpath
43
from bzrlib.trace import mutter
46
class _base_Lock(object):
153
48
def _open(self, filename, filemode):
154
self.filename = osutils.realpath(filename)
156
self.f = open(self.filename, filemode)
50
self.f = open(filename, filemode)
158
52
except IOError, e:
159
if e.errno in (errno.EACCES, errno.EPERM):
160
raise errors.LockFailed(self.filename, str(e))
161
53
if e.errno != errno.ENOENT:
164
56
# maybe this is an old branch (before may 2005)
165
trace.mutter("trying to create missing lock %r", self.filename)
167
self.f = open(self.filename, 'wb+')
57
mutter("trying to create missing branch lock %r", filename)
59
self.f = open(filename, 'wb+')
171
"""Clear the self.f attribute cleanly."""
64
from warnings import warn
65
warn("lock on %r not released" % self.f)
177
69
raise NotImplementedError()
185
class _fcntl_FileLock(_OSLock):
72
############################################################
79
class _fcntl_FileLock(_base_Lock):
187
83
def _unlock(self):
188
84
fcntl.lockf(self.f, fcntl.LOCK_UN)
88
"""Clear the self.f attribute cleanly."""
192
93
class _fcntl_WriteLock(_fcntl_FileLock):
196
97
def __init__(self, filename):
197
super(_fcntl_WriteLock, self).__init__()
198
# Check we can grab a lock before we actually open the file.
199
self.filename = osutils.realpath(filename)
200
if self.filename in _fcntl_WriteLock._open_locks:
202
raise errors.LockContention(self.filename)
203
if self.filename in _fcntl_ReadLock._open_locks:
204
if 'strict_locks' in debug.debug_flags:
206
raise errors.LockContention(self.filename)
208
trace.mutter('Write lock taken w/ an open read lock on: %s'
211
self._open(self.filename, 'rb+')
212
# reserve a slot for this lock - even if the lockf call fails,
213
# at this point unlock() will be called, because self.f is set.
214
# TODO: make this fully threadsafe, if we decide we care.
215
_fcntl_WriteLock._open_locks.add(self.filename)
98
# standard IO errors get exposed directly.
99
self._open(filename, 'wb')
217
# LOCK_NB will cause IOError to be raised if we can't grab a
219
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
101
self.filename = realpath(filename)
102
if self.filename in self.open_locks:
104
raise LockError("Lock already held.")
105
# reserve a slot for this lock - even if the lockf call fails,
106
# at thisi point unlock() will be called, because self.f is set.
107
# TODO: make this fully threadsafe, if we decide we care.
108
self.open_locks[self.filename] = self.filename
109
fcntl.lockf(self.f, fcntl.LOCK_EX)
220
110
except IOError, e:
221
if e.errno in (errno.EAGAIN, errno.EACCES):
222
# We couldn't grab the lock
224
111
# we should be more precise about whats a locking
225
112
# error and whats a random-other error
226
raise errors.LockContention(self.filename, e)
228
115
def unlock(self):
229
_fcntl_WriteLock._open_locks.remove(self.filename)
116
del self.open_locks[self.filename]
233
120
class _fcntl_ReadLock(_fcntl_FileLock):
237
122
def __init__(self, filename):
238
super(_fcntl_ReadLock, self).__init__()
239
self.filename = osutils.realpath(filename)
240
if self.filename in _fcntl_WriteLock._open_locks:
241
if 'strict_locks' in debug.debug_flags:
242
# We raise before calling _open so we don't need to
244
raise errors.LockContention(self.filename)
246
trace.mutter('Read lock taken w/ an open write lock on: %s'
248
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
249
_fcntl_ReadLock._open_locks[self.filename] += 1
123
# standard IO errors get exposed directly.
250
124
self._open(filename, 'rb')
252
# LOCK_NB will cause IOError to be raised if we can't grab a
254
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
126
fcntl.lockf(self.f, fcntl.LOCK_SH)
255
127
except IOError, e:
256
128
# we should be more precise about whats a locking
257
129
# error and whats a random-other error
258
raise errors.LockContention(self.filename, e)
260
132
def unlock(self):
261
count = _fcntl_ReadLock._open_locks[self.filename]
263
del _fcntl_ReadLock._open_locks[self.filename]
265
_fcntl_ReadLock._open_locks[self.filename] = count - 1
268
def temporary_write_lock(self):
269
"""Try to grab a write lock on the file.
271
On platforms that support it, this will upgrade to a write lock
272
without unlocking the file.
273
Otherwise, this will release the read lock, and try to acquire a
276
:return: A token which can be used to switch back to a read lock.
278
if self.filename in _fcntl_WriteLock._open_locks:
279
raise AssertionError('file already locked: %r'
282
wlock = _fcntl_TemporaryWriteLock(self)
283
except errors.LockError:
284
# We didn't unlock, so we can just return 'self'
289
class _fcntl_TemporaryWriteLock(_OSLock):
290
"""A token used when grabbing a temporary_write_lock.
292
Call restore_read_lock() when you are done with the write lock.
295
def __init__(self, read_lock):
296
super(_fcntl_TemporaryWriteLock, self).__init__()
297
self._read_lock = read_lock
298
self.filename = read_lock.filename
300
count = _fcntl_ReadLock._open_locks[self.filename]
302
# Something else also has a read-lock, so we cannot grab a
304
raise errors.LockContention(self.filename)
306
if self.filename in _fcntl_WriteLock._open_locks:
307
raise AssertionError('file already locked: %r'
310
# See if we can open the file for writing. Another process might
311
# have a read lock. We don't use self._open() because we don't want
312
# to create the file if it exists. That would have already been
313
# done by _fcntl_ReadLock
315
new_f = open(self.filename, 'rb+')
317
if e.errno in (errno.EACCES, errno.EPERM):
318
raise errors.LockFailed(self.filename, str(e))
321
# LOCK_NB will cause IOError to be raised if we can't grab a
323
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
325
# TODO: Raise a more specific error based on the type of error
326
raise errors.LockContention(self.filename, e)
327
_fcntl_WriteLock._open_locks.add(self.filename)
331
def restore_read_lock(self):
332
"""Restore the original ReadLock."""
333
# For fcntl, since we never released the read lock, just release the
334
# write lock, and return the original lock.
335
fcntl.lockf(self.f, fcntl.LOCK_UN)
337
_fcntl_WriteLock._open_locks.remove(self.filename)
338
# Avoid reference cycles
339
read_lock = self._read_lock
340
self._read_lock = None
344
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
347
if have_pywin32 and sys.platform == 'win32':
348
if os.path.supports_unicode_filenames:
349
# for Windows NT/2K/XP/etc
350
win32file_CreateFile = win32file.CreateFileW
353
win32file_CreateFile = win32file.CreateFile
355
class _w32c_FileLock(_OSLock):
357
def _open(self, filename, access, share, cflags, pymode):
358
self.filename = osutils.realpath(filename)
360
self._handle = win32file_CreateFile(filename, access, share,
361
None, win32file.OPEN_ALWAYS,
362
win32file.FILE_ATTRIBUTE_NORMAL, None)
363
except pywintypes.error, e:
364
if e.args[0] == winerror.ERROR_ACCESS_DENIED:
365
raise errors.LockFailed(filename, e)
366
if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
367
raise errors.LockContention(filename, e)
369
fd = win32file._open_osfhandle(self._handle, cflags)
370
self.f = os.fdopen(fd, pymode)
378
class _w32c_ReadLock(_w32c_FileLock):
379
def __init__(self, filename):
380
super(_w32c_ReadLock, self).__init__()
381
self._open(filename, win32file.GENERIC_READ,
382
win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
384
def temporary_write_lock(self):
385
"""Try to grab a write lock on the file.
387
On platforms that support it, this will upgrade to a write lock
388
without unlocking the file.
389
Otherwise, this will release the read lock, and try to acquire a
392
:return: A token which can be used to switch back to a read lock.
394
# I can't find a way to upgrade a read lock to a write lock without
395
# unlocking first. So here, we do just that.
398
wlock = _w32c_WriteLock(self.filename)
399
except errors.LockError:
400
return False, _w32c_ReadLock(self.filename)
404
class _w32c_WriteLock(_w32c_FileLock):
405
def __init__(self, filename):
406
super(_w32c_WriteLock, self).__init__()
408
win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
411
def restore_read_lock(self):
412
"""Restore the original ReadLock."""
413
# For win32 we had to completely let go of the original lock, so we
414
# just unlock and create a new read lock.
416
return _w32c_ReadLock(self.filename)
419
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
422
if have_ctypes_win32:
423
from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
424
LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
425
HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
426
if os.path.supports_unicode_filenames:
427
_function_name = "CreateFileW"
430
_function_name = "CreateFileA"
431
class LPTSTR(LPCSTR):
432
def __new__(cls, obj):
433
return LPCSTR.__new__(cls, obj.encode("mbcs"))
435
# CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
436
_CreateFile = ctypes.WINFUNCTYPE(
437
HANDLE, # return value
439
DWORD, # dwDesiredAccess
441
LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
442
DWORD, # dwCreationDisposition
443
DWORD, # dwFlagsAndAttributes
444
HANDLE # hTemplateFile
445
)((_function_name, ctypes.windll.kernel32))
447
INVALID_HANDLE_VALUE = -1
449
GENERIC_READ = 0x80000000
450
GENERIC_WRITE = 0x40000000
453
FILE_ATTRIBUTE_NORMAL = 128
455
ERROR_ACCESS_DENIED = 5
456
ERROR_SHARING_VIOLATION = 32
458
class _ctypes_FileLock(_OSLock):
460
def _open(self, filename, access, share, cflags, pymode):
461
self.filename = osutils.realpath(filename)
462
handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
463
FILE_ATTRIBUTE_NORMAL, 0)
464
if handle in (INVALID_HANDLE_VALUE, 0):
465
e = ctypes.WinError()
466
if e.args[0] == ERROR_ACCESS_DENIED:
467
raise errors.LockFailed(filename, e)
468
if e.args[0] == ERROR_SHARING_VIOLATION:
469
raise errors.LockContention(filename, e)
471
fd = msvcrt.open_osfhandle(handle, cflags)
472
self.f = os.fdopen(fd, pymode)
479
class _ctypes_ReadLock(_ctypes_FileLock):
480
def __init__(self, filename):
481
super(_ctypes_ReadLock, self).__init__()
482
self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
485
def temporary_write_lock(self):
486
"""Try to grab a write lock on the file.
488
On platforms that support it, this will upgrade to a write lock
489
without unlocking the file.
490
Otherwise, this will release the read lock, and try to acquire a
493
:return: A token which can be used to switch back to a read lock.
495
# I can't find a way to upgrade a read lock to a write lock without
496
# unlocking first. So here, we do just that.
499
wlock = _ctypes_WriteLock(self.filename)
500
except errors.LockError:
501
return False, _ctypes_ReadLock(self.filename)
504
class _ctypes_WriteLock(_ctypes_FileLock):
505
def __init__(self, filename):
506
super(_ctypes_WriteLock, self).__init__()
507
self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
510
def restore_read_lock(self):
511
"""Restore the original ReadLock."""
512
# For win32 we had to completely let go of the original lock, so we
513
# just unlock and create a new read lock.
515
return _ctypes_ReadLock(self.filename)
518
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
521
if len(_lock_classes) == 0:
522
raise NotImplementedError(
523
"We must have one of fcntl, pywin32, or ctypes available"
524
" to support OS locking."
528
# We default to using the first available lock class.
529
_lock_type, WriteLock, ReadLock = _lock_classes[0]
532
class _RelockDebugMixin(object):
533
"""Mixin support for -Drelock flag.
535
Add this as a base class then call self._note_lock with 'r' or 'w' when
536
acquiring a read- or write-lock. If this object was previously locked (and
537
locked the same way), and -Drelock is set, then this will trace.note a
543
def _note_lock(self, lock_type):
544
if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
549
trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
550
self._prev_lock = lock_type
552
@contextlib.contextmanager
553
def write_locked(lockable):
554
lockable.lock_write()
136
WriteLock = _fcntl_WriteLock
137
ReadLock = _fcntl_ReadLock
142
import win32con, win32file, pywintypes
145
LOCK_SH = 0 # the default
146
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
147
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
149
class _w32c_FileLock(_base_Lock):
150
def _lock(self, filename, openmode, lockmode):
152
self._open(filename, openmode)
153
self.hfile = win32file._get_osfhandle(self.f.fileno())
154
overlapped = pywintypes.OVERLAPPED()
155
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
164
overlapped = pywintypes.OVERLAPPED()
165
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
172
class _w32c_ReadLock(_w32c_FileLock):
173
def __init__(self, filename):
174
_w32c_FileLock._lock(self, filename, 'rb',
177
class _w32c_WriteLock(_w32c_FileLock):
178
def __init__(self, filename):
179
_w32c_FileLock._lock(self, filename, 'wb',
183
WriteLock = _w32c_WriteLock
184
ReadLock = _w32c_ReadLock
191
# Unfortunately, msvcrt.locking() doesn't distinguish between
192
# read locks and write locks. Also, the way the combinations
193
# work to get non-blocking is not the same, so we
194
# have to write extra special functions here.
197
class _msvc_FileLock(_base_Lock):
207
class _msvc_ReadLock(_msvc_FileLock):
208
def __init__(self, filename):
209
_msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
212
class _msvc_WriteLock(_msvc_FileLock):
213
def __init__(self, filename):
214
_msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
217
def _msvc_lock(f, flags):
219
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
220
# according to the comments, LK_RLCK is open the lock for writing.
222
# Unfortunately, msvcrt.locking() also has the side effect that it
223
# will only block for 10 seconds at most, and then it will throw an
224
# exception, this isn't terrible, though.
231
fpos = os.lseek(fn, 0,0)
234
if flags & _msvc_FileLock.LOCK_SH:
235
if flags & _msvc_FileLock.LOCK_NB:
236
lock_mode = msvcrt.LK_NBLCK
238
lock_mode = msvcrt.LK_LOCK
239
elif flags & _msvc_FileLock.LOCK_EX:
240
if flags & _msvc_FileLock.LOCK_NB:
241
lock_mode = msvcrt.LK_NBRLCK
243
lock_mode = msvcrt.LK_RLCK
245
raise ValueError('Invalid lock mode: %r' % flags)
247
msvcrt.locking(fn, lock_mode, -1)
249
os.lseek(fn, fpos, 0)
261
fpos = os.lseek(fn, 0,0)
265
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
267
os.lseek(fn, fpos, 0)
272
WriteLock = _msvc_WriteLock
273
ReadLock = _msvc_ReadLock
275
raise NotImplementedError("please write a locking method "
276
"for platform %r" % sys.platform)