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):
41
from bzrlib.trace import mutter
42
from bzrlib.errors import LockError
45
class _base_Lock(object):
151
46
def _open(self, filename, filemode):
152
self.filename = osutils.realpath(filename)
154
self.f = open(self.filename, filemode)
48
self.f = open(filename, filemode)
156
50
except IOError, e:
157
if e.errno in (errno.EACCES, errno.EPERM):
158
raise errors.LockFailed(self.filename, str(e))
159
51
if e.errno != errno.ENOENT:
162
54
# 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+')
55
mutter("trying to create missing branch lock %r", filename)
57
self.f = open(filename, 'wb+')
169
"""Clear the self.f attribute cleanly."""
174
60
def __del__(self):
176
62
from warnings import warn
177
63
warn("lock on %r not released" % self.f)
181
67
raise NotImplementedError()
189
class _fcntl_FileLock(_OSLock):
70
############################################################
77
class _fcntl_FileLock(_base_Lock):
192
81
fcntl.lockf(self.f, fcntl.LOCK_UN)
196
85
class _fcntl_WriteLock(_fcntl_FileLock):
200
86
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)
87
# standard IO errors get exposed directly.
88
self._open(filename, 'wb')
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)
90
fcntl.lockf(self.f, fcntl.LOCK_EX)
224
91
except IOError, e:
225
if e.errno in (errno.EAGAIN, errno.EACCES):
226
# We couldn't grab the lock
228
92
# we should be more precise about whats a locking
229
93
# error and whats a random-other error
230
raise errors.LockContention(self.filename, e)
233
_fcntl_WriteLock._open_locks.remove(self.filename)
237
96
class _fcntl_ReadLock(_fcntl_FileLock):
241
98
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
99
# standard IO errors get exposed directly.
254
100
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)
102
fcntl.lockf(self.f, fcntl.LOCK_SH)
259
103
except IOError, e:
260
104
# we should be more precise about whats a locking
261
105
# 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
108
WriteLock = _fcntl_WriteLock
109
ReadLock = _fcntl_ReadLock
114
import win32con, win32file, pywintypes
117
LOCK_SH = 0 # the default
118
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
119
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
121
class _w32c_FileLock(_base_Lock):
122
def _lock(self, filename, openmode, lockmode):
124
self._open(filename, openmode)
125
self.hfile = win32file._get_osfhandle(self.f.fileno())
126
overlapped = pywintypes.OVERLAPPED()
127
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
136
overlapped = pywintypes.OVERLAPPED()
137
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
144
class _w32c_ReadLock(_w32c_FileLock):
145
def __init__(self, filename):
146
_w32c_FileLock._lock(self, filename, 'rb',
149
class _w32c_WriteLock(_w32c_FileLock):
150
def __init__(self, filename):
151
_w32c_FileLock._lock(self, filename, 'wb',
155
WriteLock = _w32c_WriteLock
156
ReadLock = _w32c_ReadLock
163
# Unfortunately, msvcrt.locking() doesn't distinguish between
164
# read locks and write locks. Also, the way the combinations
165
# work to get non-blocking is not the same, so we
166
# have to write extra special functions here.
169
class _msvc_FileLock(_base_Lock):
179
class _msvc_ReadLock(_msvc_FileLock):
180
def __init__(self, filename):
181
_msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
184
class _msvc_WriteLock(_msvc_FileLock):
185
def __init__(self, filename):
186
_msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
189
def _msvc_lock(f, flags):
191
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
192
# according to the comments, LK_RLCK is open the lock for writing.
194
# Unfortunately, msvcrt.locking() also has the side effect that it
195
# will only block for 10 seconds at most, and then it will throw an
196
# exception, this isn't terrible, though.
203
fpos = os.lseek(fn, 0,0)
206
if flags & _msvc_FileLock.LOCK_SH:
207
if flags & _msvc_FileLock.LOCK_NB:
208
lock_mode = msvcrt.LK_NBLCK
210
lock_mode = msvcrt.LK_LOCK
211
elif flags & _msvc_FileLock.LOCK_EX:
212
if flags & _msvc_FileLock.LOCK_NB:
213
lock_mode = msvcrt.LK_NBRLCK
215
lock_mode = msvcrt.LK_RLCK
217
raise ValueError('Invalid lock mode: %r' % flags)
219
msvcrt.locking(fn, lock_mode, -1)
221
os.lseek(fn, fpos, 0)
233
fpos = os.lseek(fn, 0,0)
237
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
239
os.lseek(fn, fpos, 0)
244
WriteLock = _msvc_WriteLock
245
ReadLock = _msvc_ReadLock
247
raise NotImplementedError("please write a locking method "
248
"for platform %r" % sys.platform)