45
from bzrlib.hooks import Hooks
48
class LockHooks(Hooks):
53
# added in 1.8; called with a LockResult when a physical lock is
55
self['lock_acquired'] = []
57
# added in 1.8; called with a LockResult when a physical lock is
59
self['lock_released'] = []
63
"""Base class for locks.
65
:cvar hooks: Hook dictionary for operations on locks.
71
class LockResult(object):
72
"""Result of an operation on a lock; passed to a hook"""
74
def __init__(self, lock_url, details=None):
75
"""Create a lock result for lock with optional details about the lock."""
76
self.lock_url = lock_url
77
self.details = details
79
def __eq__(self, other):
80
return self.lock_url == other.lock_url and self.details == other.details
90
have_ctypes_win32 = False
91
if sys.platform == 'win32':
94
import win32con, win32file, pywintypes, winerror
101
have_ctypes_win32 = True
106
class _OSLock(object):
41
from bzrlib.trace import mutter, note, warning
42
from bzrlib.errors import LockError
44
class _base_Lock(object):
112
45
def _open(self, filename, filemode):
113
self.filename = osutils.realpath(filename)
115
self.f = open(self.filename, filemode)
47
self.f = open(filename, filemode)
117
49
except IOError, e:
118
if e.errno in (errno.EACCES, errno.EPERM):
119
raise errors.LockFailed(self.filename, str(e))
120
50
if e.errno != errno.ENOENT:
123
53
# maybe this is an old branch (before may 2005)
124
trace.mutter("trying to create missing lock %r", self.filename)
126
self.f = open(self.filename, 'wb+')
54
mutter("trying to create missing branch lock %r", filename)
56
self.f = open(filename, 'wb+')
130
"""Clear the self.f attribute cleanly."""
135
59
def __del__(self):
137
61
from warnings import warn
138
62
warn("lock on %r not released" % self.f)
142
66
raise NotImplementedError()
149
LOCK_SH = fcntl.LOCK_SH
150
LOCK_NB = fcntl.LOCK_NB
151
lock_EX = fcntl.LOCK_EX
154
class _fcntl_FileLock(_OSLock):
73
############################################################
80
class _fcntl_FileLock(_base_Lock):
157
84
fcntl.lockf(self.f, fcntl.LOCK_UN)
161
88
class _fcntl_WriteLock(_fcntl_FileLock):
165
89
def __init__(self, filename):
166
super(_fcntl_WriteLock, self).__init__()
167
# Check we can grab a lock before we actually open the file.
168
self.filename = osutils.realpath(filename)
169
if self.filename in _fcntl_WriteLock._open_locks:
171
raise errors.LockContention(self.filename)
173
self._open(self.filename, 'rb+')
174
# reserve a slot for this lock - even if the lockf call fails,
175
# at thisi point unlock() will be called, because self.f is set.
176
# TODO: make this fully threadsafe, if we decide we care.
177
_fcntl_WriteLock._open_locks.add(self.filename)
90
# standard IO errors get exposed directly.
91
self._open(filename, 'wb')
179
# LOCK_NB will cause IOError to be raised if we can't grab a
181
fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
93
fcntl.lockf(self.f, fcntl.LOCK_EX)
182
94
except IOError, e:
183
if e.errno in (errno.EAGAIN, errno.EACCES):
184
# We couldn't grab the lock
186
95
# we should be more precise about whats a locking
187
96
# error and whats a random-other error
188
raise errors.LockContention(e)
191
_fcntl_WriteLock._open_locks.remove(self.filename)
195
99
class _fcntl_ReadLock(_fcntl_FileLock):
199
101
def __init__(self, filename):
200
super(_fcntl_ReadLock, self).__init__()
201
self.filename = osutils.realpath(filename)
202
_fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
203
_fcntl_ReadLock._open_locks[self.filename] += 1
102
# standard IO errors get exposed directly.
204
103
self._open(filename, 'rb')
206
# LOCK_NB will cause IOError to be raised if we can't grab a
208
fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
105
fcntl.lockf(self.f, fcntl.LOCK_SH)
209
106
except IOError, e:
210
107
# we should be more precise about whats a locking
211
108
# error and whats a random-other error
212
raise errors.LockContention(e)
215
count = _fcntl_ReadLock._open_locks[self.filename]
217
del _fcntl_ReadLock._open_locks[self.filename]
219
_fcntl_ReadLock._open_locks[self.filename] = count - 1
222
def temporary_write_lock(self):
223
"""Try to grab a write lock on the file.
225
On platforms that support it, this will upgrade to a write lock
226
without unlocking the file.
227
Otherwise, this will release the read lock, and try to acquire a
230
:return: A token which can be used to switch back to a read lock.
232
if self.filename in _fcntl_WriteLock._open_locks:
233
raise AssertionError('file already locked: %r'
236
wlock = _fcntl_TemporaryWriteLock(self)
237
except errors.LockError:
238
# We didn't unlock, so we can just return 'self'
243
class _fcntl_TemporaryWriteLock(_OSLock):
244
"""A token used when grabbing a temporary_write_lock.
246
Call restore_read_lock() when you are done with the write lock.
249
def __init__(self, read_lock):
250
super(_fcntl_TemporaryWriteLock, self).__init__()
251
self._read_lock = read_lock
252
self.filename = read_lock.filename
254
count = _fcntl_ReadLock._open_locks[self.filename]
256
# Something else also has a read-lock, so we cannot grab a
258
raise errors.LockContention(self.filename)
260
if self.filename in _fcntl_WriteLock._open_locks:
261
raise AssertionError('file already locked: %r'
264
# See if we can open the file for writing. Another process might
265
# have a read lock. We don't use self._open() because we don't want
266
# to create the file if it exists. That would have already been
267
# done by _fcntl_ReadLock
269
new_f = open(self.filename, 'rb+')
271
if e.errno in (errno.EACCES, errno.EPERM):
272
raise errors.LockFailed(self.filename, str(e))
275
# LOCK_NB will cause IOError to be raised if we can't grab a
277
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
279
# TODO: Raise a more specific error based on the type of error
280
raise errors.LockContention(e)
281
_fcntl_WriteLock._open_locks.add(self.filename)
285
def restore_read_lock(self):
286
"""Restore the original ReadLock."""
287
# For fcntl, since we never released the read lock, just release the
288
# write lock, and return the original lock.
289
fcntl.lockf(self.f, fcntl.LOCK_UN)
291
_fcntl_WriteLock._open_locks.remove(self.filename)
292
# Avoid reference cycles
293
read_lock = self._read_lock
294
self._read_lock = None
298
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
301
if have_pywin32 and sys.platform == 'win32':
302
LOCK_SH = 0 # the default
303
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
304
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
307
class _w32c_FileLock(_OSLock):
309
def _lock(self, filename, openmode, lockmode):
310
self._open(filename, openmode)
312
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
313
overlapped = pywintypes.OVERLAPPED()
315
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
317
except pywintypes.error, e:
319
if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
320
raise errors.LockContention(filename)
321
## import pdb; pdb.set_trace()
325
raise errors.LockContention(e)
328
overlapped = pywintypes.OVERLAPPED()
330
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
333
raise errors.LockContention(e)
336
class _w32c_ReadLock(_w32c_FileLock):
337
def __init__(self, filename):
338
super(_w32c_ReadLock, self).__init__()
339
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
341
def temporary_write_lock(self):
342
"""Try to grab a write lock on the file.
344
On platforms that support it, this will upgrade to a write lock
345
without unlocking the file.
346
Otherwise, this will release the read lock, and try to acquire a
349
:return: A token which can be used to switch back to a read lock.
351
# I can't find a way to upgrade a read lock to a write lock without
352
# unlocking first. So here, we do just that.
355
wlock = _w32c_WriteLock(self.filename)
356
except errors.LockError:
357
return False, _w32c_ReadLock(self.filename)
361
class _w32c_WriteLock(_w32c_FileLock):
362
def __init__(self, filename):
363
super(_w32c_WriteLock, self).__init__()
364
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
366
def restore_read_lock(self):
367
"""Restore the original ReadLock."""
368
# For win32 we had to completely let go of the original lock, so we
369
# just unlock and create a new read lock.
371
return _w32c_ReadLock(self.filename)
374
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
377
if have_ctypes_win32:
378
# These constants were copied from the win32con.py module.
379
LOCKFILE_FAIL_IMMEDIATELY = 1
380
LOCKFILE_EXCLUSIVE_LOCK = 2
381
# Constant taken from winerror.py module
382
ERROR_LOCK_VIOLATION = 33
385
LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
386
LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
387
_LockFileEx = ctypes.windll.kernel32.LockFileEx
388
_UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
389
_GetLastError = ctypes.windll.kernel32.GetLastError
391
### Define the OVERLAPPED structure.
392
# http://msdn2.microsoft.com/en-us/library/ms684342.aspx
393
# typedef struct _OVERLAPPED {
394
# ULONG_PTR Internal;
395
# ULONG_PTR InternalHigh;
406
class _inner_struct(ctypes.Structure):
407
_fields_ = [('Offset', ctypes.c_uint), # DWORD
408
('OffsetHigh', ctypes.c_uint), # DWORD
411
class _inner_union(ctypes.Union):
412
_fields_ = [('anon_struct', _inner_struct), # struct
413
('Pointer', ctypes.c_void_p), # PVOID
416
class OVERLAPPED(ctypes.Structure):
417
_fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
418
('InternalHigh', ctypes.c_void_p), # ULONG_PTR
419
('_inner_union', _inner_union),
420
('hEvent', ctypes.c_void_p), # HANDLE
423
class _ctypes_FileLock(_OSLock):
425
def _lock(self, filename, openmode, lockmode):
426
self._open(filename, openmode)
428
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
429
overlapped = OVERLAPPED()
430
result = _LockFileEx(self.hfile, # HANDLE hFile
431
lockmode, # DWORD dwFlags
432
0, # DWORD dwReserved
433
0x7fffffff, # DWORD nNumberOfBytesToLockLow
434
0x00000000, # DWORD nNumberOfBytesToLockHigh
435
ctypes.byref(overlapped), # lpOverlapped
439
last_err = _GetLastError()
440
if last_err in (ERROR_LOCK_VIOLATION,):
441
raise errors.LockContention(filename)
442
raise errors.LockContention('Unknown locking error: %s'
446
overlapped = OVERLAPPED()
447
result = _UnlockFileEx(self.hfile, # HANDLE hFile
448
0, # DWORD dwReserved
449
0x7fffffff, # DWORD nNumberOfBytesToLockLow
450
0x00000000, # DWORD nNumberOfBytesToLockHigh
451
ctypes.byref(overlapped), # lpOverlapped
456
last_err = _GetLastError()
457
raise errors.LockContention('Unknown unlocking error: %s'
461
class _ctypes_ReadLock(_ctypes_FileLock):
462
def __init__(self, filename):
463
super(_ctypes_ReadLock, self).__init__()
464
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
466
def temporary_write_lock(self):
467
"""Try to grab a write lock on the file.
469
On platforms that support it, this will upgrade to a write lock
470
without unlocking the file.
471
Otherwise, this will release the read lock, and try to acquire a
474
:return: A token which can be used to switch back to a read lock.
476
# I can't find a way to upgrade a read lock to a write lock without
477
# unlocking first. So here, we do just that.
480
wlock = _ctypes_WriteLock(self.filename)
481
except errors.LockError:
482
return False, _ctypes_ReadLock(self.filename)
485
class _ctypes_WriteLock(_ctypes_FileLock):
486
def __init__(self, filename):
487
super(_ctypes_WriteLock, self).__init__()
488
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
490
def restore_read_lock(self):
491
"""Restore the original ReadLock."""
492
# For win32 we had to completely let go of the original lock, so we
493
# just unlock and create a new read lock.
495
return _ctypes_ReadLock(self.filename)
498
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
501
if len(_lock_classes) == 0:
502
raise NotImplementedError(
503
"We must have one of fcntl, pywin32, or ctypes available"
504
" to support OS locking."
508
# We default to using the first available lock class.
509
_lock_type, WriteLock, ReadLock = _lock_classes[0]
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)