36
from __future__ import absolute_import
50
from .hooks import Hooks
51
from .i18n import gettext
54
class LockHooks(Hooks):
57
Hooks.__init__(self, "breezy.lock", "Lock.hooks")
60
"Called with a breezy.lock.LockResult when a physical lock is "
64
"Called with a breezy.lock.LockResult when a physical lock is "
68
"Called with a breezy.lock.LockResult when a physical lock is "
73
"""Base class for locks.
75
:cvar hooks: Hook dictionary for operations on locks.
81
class LockResult(object):
82
"""Result of an operation on a lock; passed to a hook"""
84
def __init__(self, lock_url, details=None):
85
"""Create a lock result for lock with optional details about the lock."""
86
self.lock_url = lock_url
87
self.details = details
89
def __eq__(self, other):
90
return self.lock_url == other.lock_url and self.details == other.details
93
return '%s(%s, %s)' % (self.__class__.__name__,
94
self.lock_url, self.details)
97
class LogicalLockResult(object):
98
"""The result of a lock_read/lock_write/lock_tree_write call on lockables.
100
:ivar unlock: A callable which will unlock the lock.
103
def __init__(self, unlock, token=None):
108
return "LogicalLockResult(%s)" % (self.unlock)
113
def __exit__(self, exc_type, exc_val, exc_tb):
114
# If there was an error raised, prefer the original one
117
except BaseException:
123
def cant_unlock_not_held(locked_object):
124
"""An attempt to unlock failed because the object was not locked.
126
This provides a policy point from which we can generate either a warning or
129
# This is typically masking some other error and called from a finally
130
# block, so it's useful to have the option not to generate a new error
131
# here. You can use -Werror to make it fatal. It should possibly also
133
if 'unlock' in debug.debug_flags:
134
warnings.warn("%r is already unlocked" % (locked_object,),
137
raise errors.LockNotHeld(locked_object)
146
have_ctypes_win32 = False
147
if sys.platform == 'win32':
151
have_ctypes_win32 = True
156
class _OSLock(object):
41
from bzrlib.trace import mutter, note, warning
42
from bzrlib.errors import LockError
44
class _base_Lock(object):
162
45
def _open(self, filename, filemode):
163
self.filename = osutils.realpath(filename)
165
self.f = open(self.filename, filemode)
47
self.f = open(filename, filemode)
168
if e.errno in (errno.EACCES, errno.EPERM):
169
raise errors.LockFailed(self.filename, str(e))
170
50
if e.errno != errno.ENOENT:
173
53
# maybe this is an old branch (before may 2005)
174
trace.mutter("trying to create missing lock %r", self.filename)
176
self.f = open(self.filename, 'wb+')
54
mutter("trying to create missing branch lock %r", filename)
56
self.f = open(filename, 'wb+')
180
"""Clear the self.f attribute cleanly."""
61
from warnings import warn
62
warn("lock on %r not released" % self.f)
186
66
raise NotImplementedError()
194
class _fcntl_FileLock(_OSLock):
73
############################################################
80
class _fcntl_FileLock(_base_Lock):
197
84
fcntl.lockf(self.f, fcntl.LOCK_UN)
200
88
class _fcntl_WriteLock(_fcntl_FileLock):
204
89
def __init__(self, filename):
205
super(_fcntl_WriteLock, self).__init__()
206
# Check we can grab a lock before we actually open the file.
207
self.filename = osutils.realpath(filename)
208
if self.filename in _fcntl_WriteLock._open_locks:
210
raise errors.LockContention(self.filename)
211
if self.filename in _fcntl_ReadLock._open_locks:
212
if 'strict_locks' in debug.debug_flags:
214
raise errors.LockContention(self.filename)
216
trace.mutter('Write lock taken w/ an open read lock on: %s'
219
self._open(self.filename, 'rb+')
220
# reserve a slot for this lock - even if the lockf call fails,
221
# at this point unlock() will be called, because self.f is set.
222
# TODO: make this fully threadsafe, if we decide we care.
223
_fcntl_WriteLock._open_locks.add(self.filename)
90
# standard IO errors get exposed directly.
91
self._open(filename, 'wb')
225
# LOCK_NB will cause IOError to be raised if we can't grab a
227
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
229
if e.errno in (errno.EAGAIN, errno.EACCES):
230
# We couldn't grab the lock
93
fcntl.lockf(self.f, fcntl.LOCK_EX)
232
95
# we should be more precise about whats a locking
233
96
# error and whats a random-other error
234
raise errors.LockContention(self.filename, e)
237
_fcntl_WriteLock._open_locks.remove(self.filename)
240
99
class _fcntl_ReadLock(_fcntl_FileLock):
244
101
def __init__(self, filename):
245
super(_fcntl_ReadLock, self).__init__()
246
self.filename = osutils.realpath(filename)
247
if self.filename in _fcntl_WriteLock._open_locks:
248
if 'strict_locks' in debug.debug_flags:
249
# We raise before calling _open so we don't need to
251
raise errors.LockContention(self.filename)
253
trace.mutter('Read lock taken w/ an open write lock on: %s'
255
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
256
_fcntl_ReadLock._open_locks[self.filename] += 1
102
# standard IO errors get exposed directly.
257
103
self._open(filename, 'rb')
259
# LOCK_NB will cause IOError to be raised if we can't grab a
261
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
105
fcntl.lockf(self.f, fcntl.LOCK_SH)
263
107
# we should be more precise about whats a locking
264
108
# error and whats a random-other error
265
raise errors.LockContention(self.filename, e)
268
count = _fcntl_ReadLock._open_locks[self.filename]
270
del _fcntl_ReadLock._open_locks[self.filename]
272
_fcntl_ReadLock._open_locks[self.filename] = count - 1
275
def temporary_write_lock(self):
276
"""Try to grab a write lock on the file.
278
On platforms that support it, this will upgrade to a write lock
279
without unlocking the file.
280
Otherwise, this will release the read lock, and try to acquire a
283
:return: A token which can be used to switch back to a read lock.
285
if self.filename in _fcntl_WriteLock._open_locks:
286
raise AssertionError('file already locked: %r'
289
wlock = _fcntl_TemporaryWriteLock(self)
290
except errors.LockError:
291
# We didn't unlock, so we can just return 'self'
295
class _fcntl_TemporaryWriteLock(_OSLock):
296
"""A token used when grabbing a temporary_write_lock.
298
Call restore_read_lock() when you are done with the write lock.
301
def __init__(self, read_lock):
302
super(_fcntl_TemporaryWriteLock, self).__init__()
303
self._read_lock = read_lock
304
self.filename = read_lock.filename
306
count = _fcntl_ReadLock._open_locks[self.filename]
308
# Something else also has a read-lock, so we cannot grab a
310
raise errors.LockContention(self.filename)
312
if self.filename in _fcntl_WriteLock._open_locks:
313
raise AssertionError('file already locked: %r'
316
# See if we can open the file for writing. Another process might
317
# have a read lock. We don't use self._open() because we don't want
318
# to create the file if it exists. That would have already been
319
# done by _fcntl_ReadLock
321
new_f = open(self.filename, 'rb+')
323
if e.errno in (errno.EACCES, errno.EPERM):
324
raise errors.LockFailed(self.filename, str(e))
327
# LOCK_NB will cause IOError to be raised if we can't grab a
329
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
331
# TODO: Raise a more specific error based on the type of error
332
raise errors.LockContention(self.filename, e)
333
_fcntl_WriteLock._open_locks.add(self.filename)
337
def restore_read_lock(self):
338
"""Restore the original ReadLock."""
339
# For fcntl, since we never released the read lock, just release
340
# the write lock, and return the original lock.
341
fcntl.lockf(self.f, fcntl.LOCK_UN)
343
_fcntl_WriteLock._open_locks.remove(self.filename)
344
# Avoid reference cycles
345
read_lock = self._read_lock
346
self._read_lock = None
349
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
352
if have_ctypes_win32:
353
from ctypes.wintypes import DWORD, LPWSTR
354
LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
355
HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
356
_function_name = "CreateFileW"
358
# CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
359
_CreateFile = ctypes.WINFUNCTYPE(
360
HANDLE, # return value
362
DWORD, # dwDesiredAccess
364
LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
365
DWORD, # dwCreationDisposition
366
DWORD, # dwFlagsAndAttributes
367
HANDLE # hTemplateFile
368
)((_function_name, ctypes.windll.kernel32))
370
INVALID_HANDLE_VALUE = -1
372
GENERIC_READ = 0x80000000
373
GENERIC_WRITE = 0x40000000
376
FILE_ATTRIBUTE_NORMAL = 128
378
ERROR_ACCESS_DENIED = 5
379
ERROR_SHARING_VIOLATION = 32
381
class _ctypes_FileLock(_OSLock):
383
def _open(self, filename, access, share, cflags, pymode):
384
self.filename = osutils.realpath(filename)
385
handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
386
FILE_ATTRIBUTE_NORMAL, 0)
387
if handle in (INVALID_HANDLE_VALUE, 0):
388
e = ctypes.WinError()
389
if e.args[0] == ERROR_ACCESS_DENIED:
390
raise errors.LockFailed(filename, e)
391
if e.args[0] == ERROR_SHARING_VIOLATION:
392
raise errors.LockContention(filename, e)
394
fd = msvcrt.open_osfhandle(handle, cflags)
395
self.f = os.fdopen(fd, pymode)
401
class _ctypes_ReadLock(_ctypes_FileLock):
402
def __init__(self, filename):
403
super(_ctypes_ReadLock, self).__init__()
404
self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
407
def temporary_write_lock(self):
408
"""Try to grab a write lock on the file.
410
On platforms that support it, this will upgrade to a write lock
411
without unlocking the file.
412
Otherwise, this will release the read lock, and try to acquire a
415
:return: A token which can be used to switch back to a read lock.
417
# I can't find a way to upgrade a read lock to a write lock without
418
# unlocking first. So here, we do just that.
421
wlock = _ctypes_WriteLock(self.filename)
422
except errors.LockError:
423
return False, _ctypes_ReadLock(self.filename)
426
class _ctypes_WriteLock(_ctypes_FileLock):
427
def __init__(self, filename):
428
super(_ctypes_WriteLock, self).__init__()
429
self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
432
def restore_read_lock(self):
433
"""Restore the original ReadLock."""
434
# For win32 we had to completely let go of the original lock, so we
435
# just unlock and create a new read lock.
437
return _ctypes_ReadLock(self.filename)
439
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
442
if len(_lock_classes) == 0:
443
raise NotImplementedError(
444
"We must have one of fcntl or ctypes available"
445
" to support OS locking."
449
# We default to using the first available lock class.
450
_lock_type, WriteLock, ReadLock = _lock_classes[0]
453
class _RelockDebugMixin(object):
454
"""Mixin support for -Drelock flag.
456
Add this as a base class then call self._note_lock with 'r' or 'w' when
457
acquiring a read- or write-lock. If this object was previously locked (and
458
locked the same way), and -Drelock is set, then this will trace.note a
464
def _note_lock(self, lock_type):
465
if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
470
trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
471
self._prev_lock = lock_type
474
@contextlib.contextmanager
475
def write_locked(lockable):
476
lockable.lock_write()
111
WriteLock = _fcntl_WriteLock
112
ReadLock = _fcntl_ReadLock
117
import win32con, win32file, pywintypes
120
LOCK_SH = 0 # the default
121
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
122
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
124
class _w32c_FileLock(_base_Lock):
125
def _lock(self, filename, openmode, lockmode):
127
self._open(filename, openmode)
128
self.hfile = win32file._get_osfhandle(self.f.fileno())
129
overlapped = pywintypes.OVERLAPPED()
130
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
136
overlapped = pywintypes.OVERLAPPED()
137
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
145
class _w32c_ReadLock(_w32c_FileLock):
146
def __init__(self, filename):
147
_w32c_FileLock._lock(self, filename, 'rb',
150
class _w32c_WriteLock(_w32c_FileLock):
151
def __init__(self, filename):
152
_w32c_FileLock._lock(self, filename, 'wb',
157
WriteLock = _w32c_WriteLock
158
ReadLock = _w32c_ReadLock
165
# Unfortunately, msvcrt.locking() doesn't distinguish between
166
# read locks and write locks. Also, the way the combinations
167
# work to get non-blocking is not the same, so we
168
# have to write extra special functions here.
171
class _msvc_FileLock(_base_Lock):
181
class _msvc_ReadLock(_msvc_FileLock):
182
def __init__(self, filename):
183
_msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
186
class _msvc_WriteLock(_msvc_FileLock):
187
def __init__(self, filename):
188
_msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
192
def _msvc_lock(f, flags):
194
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
195
# according to the comments, LK_RLCK is open the lock for writing.
197
# Unfortunately, msvcrt.locking() also has the side effect that it
198
# will only block for 10 seconds at most, and then it will throw an
199
# exception, this isn't terrible, though.
206
fpos = os.lseek(fn, 0,0)
209
if flags & _msvc_FileLock.LOCK_SH:
210
if flags & _msvc_FileLock.LOCK_NB:
211
lock_mode = msvcrt.LK_NBLCK
213
lock_mode = msvcrt.LK_LOCK
214
elif flags & _msvc_FileLock.LOCK_EX:
215
if flags & _msvc_FileLock.LOCK_NB:
216
lock_mode = msvcrt.LK_NBRLCK
218
lock_mode = msvcrt.LK_RLCK
220
raise ValueError('Invalid lock mode: %r' % flags)
222
msvcrt.locking(fn, lock_mode, -1)
224
os.lseek(fn, fpos, 0)
236
fpos = os.lseek(fn, 0,0)
240
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
242
os.lseek(fn, fpos, 0)
248
WriteLock = _msvc_WriteLock
249
ReadLock = _msvc_ReadLock
251
raise NotImplementedError("please write a locking method "
252
"for platform %r" % sys.platform)