41
from bzrlib.errors import LockError
42
from bzrlib.osutils import realpath
43
from bzrlib.trace import mutter
46
class _base_Lock(object):
45
from bzrlib.hooks import HookPoint, Hooks
48
class LockHooks(Hooks):
52
self.create_hook(HookPoint('lock_acquired',
53
"Called with a bzrlib.lock.LockResult when a physical lock is "
54
"acquired.", (1, 8), None))
55
self.create_hook(HookPoint('lock_released',
56
"Called with a bzrlib.lock.LockResult when a physical lock is "
57
"released.", (1, 8), None))
58
self.create_hook(HookPoint('lock_broken',
59
"Called with a bzrlib.lock.LockResult when a physical lock is "
60
"broken.", (1, 15), None))
64
"""Base class for locks.
66
:cvar hooks: Hook dictionary for operations on locks.
72
class LockResult(object):
73
"""Result of an operation on a lock; passed to a hook"""
75
def __init__(self, lock_url, details=None):
76
"""Create a lock result for lock with optional details about the lock."""
77
self.lock_url = lock_url
78
self.details = details
80
def __eq__(self, other):
81
return self.lock_url == other.lock_url and self.details == other.details
84
return '%s(%s%s)' % (self.__class__.__name__,
85
self.lock_url, self.details)
95
have_ctypes_win32 = False
96
if sys.platform == 'win32':
99
import win32con, win32file, pywintypes, winerror
106
have_ctypes_win32 = True
111
class _OSLock(object):
48
117
def _open(self, filename, filemode):
118
self.filename = osutils.realpath(filename)
50
self.f = open(filename, filemode)
120
self.f = open(self.filename, filemode)
52
122
except IOError, e:
123
if e.errno in (errno.EACCES, errno.EPERM):
124
raise errors.LockFailed(self.filename, str(e))
53
125
if e.errno != errno.ENOENT:
56
128
# maybe this is an old branch (before may 2005)
57
mutter("trying to create missing branch lock %r", filename)
59
self.f = open(filename, 'wb+')
129
trace.mutter("trying to create missing lock %r", self.filename)
131
self.f = open(self.filename, 'wb+')
135
"""Clear the self.f attribute cleanly."""
62
140
def __del__(self):
64
142
from warnings import warn
65
143
warn("lock on %r not released" % self.f)
69
147
raise NotImplementedError()
72
############################################################
79
class _fcntl_FileLock(_base_Lock):
154
LOCK_SH = fcntl.LOCK_SH
155
LOCK_NB = fcntl.LOCK_NB
156
lock_EX = fcntl.LOCK_EX
159
class _fcntl_FileLock(_OSLock):
83
161
def _unlock(self):
84
162
fcntl.lockf(self.f, fcntl.LOCK_UN)
88
"""Clear the self.f attribute cleanly."""
93
166
class _fcntl_WriteLock(_fcntl_FileLock):
97
170
def __init__(self, filename):
98
# standard IO errors get exposed directly.
99
self._open(filename, 'wb')
171
super(_fcntl_WriteLock, self).__init__()
172
# Check we can grab a lock before we actually open the file.
173
self.filename = osutils.realpath(filename)
174
if self.filename in _fcntl_WriteLock._open_locks:
176
raise errors.LockContention(self.filename)
178
self._open(self.filename, 'rb+')
179
# reserve a slot for this lock - even if the lockf call fails,
180
# at thisi point unlock() will be called, because self.f is set.
181
# TODO: make this fully threadsafe, if we decide we care.
182
_fcntl_WriteLock._open_locks.add(self.filename)
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)
184
# LOCK_NB will cause IOError to be raised if we can't grab a
186
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
110
187
except IOError, e:
188
if e.errno in (errno.EAGAIN, errno.EACCES):
189
# We couldn't grab the lock
111
191
# we should be more precise about whats a locking
112
192
# error and whats a random-other error
193
raise errors.LockContention(self.filename, e)
115
195
def unlock(self):
116
del self.open_locks[self.filename]
196
_fcntl_WriteLock._open_locks.remove(self.filename)
120
200
class _fcntl_ReadLock(_fcntl_FileLock):
122
204
def __init__(self, filename):
123
# standard IO errors get exposed directly.
205
super(_fcntl_ReadLock, self).__init__()
206
self.filename = osutils.realpath(filename)
207
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
208
_fcntl_ReadLock._open_locks[self.filename] += 1
124
209
self._open(filename, 'rb')
126
fcntl.lockf(self.f, fcntl.LOCK_SH)
211
# LOCK_NB will cause IOError to be raised if we can't grab a
213
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
127
214
except IOError, e:
128
215
# we should be more precise about whats a locking
129
216
# error and whats a random-other error
217
raise errors.LockContention(self.filename, e)
132
219
def unlock(self):
220
count = _fcntl_ReadLock._open_locks[self.filename]
222
del _fcntl_ReadLock._open_locks[self.filename]
224
_fcntl_ReadLock._open_locks[self.filename] = count - 1
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)
227
def temporary_write_lock(self):
228
"""Try to grab a write lock on the file.
230
On platforms that support it, this will upgrade to a write lock
231
without unlocking the file.
232
Otherwise, this will release the read lock, and try to acquire a
235
:return: A token which can be used to switch back to a read lock.
237
if self.filename in _fcntl_WriteLock._open_locks:
238
raise AssertionError('file already locked: %r'
241
wlock = _fcntl_TemporaryWriteLock(self)
242
except errors.LockError:
243
# We didn't unlock, so we can just return 'self'
248
class _fcntl_TemporaryWriteLock(_OSLock):
249
"""A token used when grabbing a temporary_write_lock.
251
Call restore_read_lock() when you are done with the write lock.
254
def __init__(self, read_lock):
255
super(_fcntl_TemporaryWriteLock, self).__init__()
256
self._read_lock = read_lock
257
self.filename = read_lock.filename
259
count = _fcntl_ReadLock._open_locks[self.filename]
261
# Something else also has a read-lock, so we cannot grab a
263
raise errors.LockContention(self.filename)
265
if self.filename in _fcntl_WriteLock._open_locks:
266
raise AssertionError('file already locked: %r'
269
# See if we can open the file for writing. Another process might
270
# have a read lock. We don't use self._open() because we don't want
271
# to create the file if it exists. That would have already been
272
# done by _fcntl_ReadLock
274
new_f = open(self.filename, 'rb+')
276
if e.errno in (errno.EACCES, errno.EPERM):
277
raise errors.LockFailed(self.filename, str(e))
280
# LOCK_NB will cause IOError to be raised if we can't grab a
282
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
284
# TODO: Raise a more specific error based on the type of error
285
raise errors.LockContention(self.filename, e)
286
_fcntl_WriteLock._open_locks.add(self.filename)
290
def restore_read_lock(self):
291
"""Restore the original ReadLock."""
292
# For fcntl, since we never released the read lock, just release the
293
# write lock, and return the original lock.
294
fcntl.lockf(self.f, fcntl.LOCK_UN)
296
_fcntl_WriteLock._open_locks.remove(self.filename)
297
# Avoid reference cycles
298
read_lock = self._read_lock
299
self._read_lock = None
303
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
306
if have_pywin32 and sys.platform == 'win32':
307
LOCK_SH = 0 # the default
308
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
309
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
312
class _w32c_FileLock(_OSLock):
314
def _lock(self, filename, openmode, lockmode):
315
self._open(filename, openmode)
317
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
318
overlapped = pywintypes.OVERLAPPED()
320
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
322
except pywintypes.error, e:
324
if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
325
raise errors.LockContention(filename)
326
## import pdb; pdb.set_trace()
330
raise errors.LockContention(filename, e)
333
overlapped = pywintypes.OVERLAPPED()
335
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
338
raise errors.LockContention(self.filename, e)
341
class _w32c_ReadLock(_w32c_FileLock):
342
def __init__(self, filename):
343
super(_w32c_ReadLock, self).__init__()
344
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
346
def temporary_write_lock(self):
347
"""Try to grab a write lock on the file.
349
On platforms that support it, this will upgrade to a write lock
350
without unlocking the file.
351
Otherwise, this will release the read lock, and try to acquire a
354
:return: A token which can be used to switch back to a read lock.
356
# I can't find a way to upgrade a read lock to a write lock without
357
# unlocking first. So here, we do just that.
360
wlock = _w32c_WriteLock(self.filename)
361
except errors.LockError:
362
return False, _w32c_ReadLock(self.filename)
366
class _w32c_WriteLock(_w32c_FileLock):
367
def __init__(self, filename):
368
super(_w32c_WriteLock, self).__init__()
369
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
371
def restore_read_lock(self):
372
"""Restore the original ReadLock."""
373
# For win32 we had to completely let go of the original lock, so we
374
# just unlock and create a new read lock.
376
return _w32c_ReadLock(self.filename)
379
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
382
if have_ctypes_win32:
383
# These constants were copied from the win32con.py module.
384
LOCKFILE_FAIL_IMMEDIATELY = 1
385
LOCKFILE_EXCLUSIVE_LOCK = 2
386
# Constant taken from winerror.py module
387
ERROR_LOCK_VIOLATION = 33
390
LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
391
LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
392
_LockFileEx = ctypes.windll.kernel32.LockFileEx
393
_UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
395
### Define the OVERLAPPED structure.
396
# http://msdn2.microsoft.com/en-us/library/ms684342.aspx
397
# typedef struct _OVERLAPPED {
398
# ULONG_PTR Internal;
399
# ULONG_PTR InternalHigh;
410
class _inner_struct(ctypes.Structure):
411
_fields_ = [('Offset', ctypes.c_uint), # DWORD
412
('OffsetHigh', ctypes.c_uint), # DWORD
415
class _inner_union(ctypes.Union):
416
_fields_ = [('anon_struct', _inner_struct), # struct
417
('Pointer', ctypes.c_void_p), # PVOID
420
class OVERLAPPED(ctypes.Structure):
421
_fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
422
('InternalHigh', ctypes.c_void_p), # ULONG_PTR
423
('_inner_union', _inner_union),
424
('hEvent', ctypes.c_void_p), # HANDLE
427
class _ctypes_FileLock(_OSLock):
429
def _lock(self, filename, openmode, lockmode):
430
self._open(filename, openmode)
432
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
433
overlapped = OVERLAPPED()
434
result = _LockFileEx(self.hfile, # HANDLE hFile
435
lockmode, # DWORD dwFlags
436
0, # DWORD dwReserved
437
0x7fffffff, # DWORD nNumberOfBytesToLockLow
438
0x00000000, # DWORD nNumberOfBytesToLockHigh
439
ctypes.byref(overlapped), # lpOverlapped
442
last_err = ctypes.GetLastError()
444
if last_err in (ERROR_LOCK_VIOLATION,):
445
raise errors.LockContention(filename)
446
raise errors.LockContention(filename,
447
'Unknown locking error: %s' % (last_err,))
450
overlapped = OVERLAPPED()
451
result = _UnlockFileEx(self.hfile, # HANDLE hFile
452
0, # DWORD dwReserved
453
0x7fffffff, # DWORD nNumberOfBytesToLockLow
454
0x00000000, # DWORD nNumberOfBytesToLockHigh
455
ctypes.byref(overlapped), # lpOverlapped
458
last_err = ctypes.GetLastError()
460
raise errors.LockContention(self.filename,
461
'Unknown unlocking error: %s' % (last_err,))
465
class _ctypes_ReadLock(_ctypes_FileLock):
466
def __init__(self, filename):
467
super(_ctypes_ReadLock, self).__init__()
468
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
470
def temporary_write_lock(self):
471
"""Try to grab a write lock on the file.
473
On platforms that support it, this will upgrade to a write lock
474
without unlocking the file.
475
Otherwise, this will release the read lock, and try to acquire a
478
:return: A token which can be used to switch back to a read lock.
480
# I can't find a way to upgrade a read lock to a write lock without
481
# unlocking first. So here, we do just that.
484
wlock = _ctypes_WriteLock(self.filename)
485
except errors.LockError:
486
return False, _ctypes_ReadLock(self.filename)
489
class _ctypes_WriteLock(_ctypes_FileLock):
490
def __init__(self, filename):
491
super(_ctypes_WriteLock, self).__init__()
492
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
494
def restore_read_lock(self):
495
"""Restore the original ReadLock."""
496
# For win32 we had to completely let go of the original lock, so we
497
# just unlock and create a new read lock.
499
return _ctypes_ReadLock(self.filename)
502
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
505
if len(_lock_classes) == 0:
506
raise NotImplementedError(
507
"We must have one of fcntl, pywin32, or ctypes available"
508
" to support OS locking."
512
# We default to using the first available lock class.
513
_lock_type, WriteLock, ReadLock = _lock_classes[0]