41
from bzrlib.errors import LockError, LockContention
42
from bzrlib.osutils import realpath
43
from bzrlib.trace import mutter
46
47
class _base_Lock(object):
48
53
def _open(self, filename, filemode):
54
self.filename = osutils.realpath(filename)
50
self.f = open(filename, filemode)
56
self.f = open(self.filename, filemode)
59
if e.errno in (errno.EACCES, errno.EPERM):
60
raise errors.ReadOnlyLockError(self.filename, str(e))
53
61
if e.errno != errno.ENOENT:
56
64
# 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+')
65
trace.mutter("trying to create missing lock %r", self.filename)
67
self.f = open(self.filename, 'wb+')
71
"""Clear the self.f attribute cleanly."""
64
78
from warnings import warn
65
79
warn("lock on %r not released" % self.f)
69
83
raise NotImplementedError()
72
############################################################
92
import win32con, win32file, pywintypes, winerror, msvcrt
107
LOCK_SH = fcntl.LOCK_SH
108
LOCK_NB = fcntl.LOCK_NB
109
lock_EX = fcntl.LOCK_EX
79
112
class _fcntl_FileLock(_base_Lock):
83
114
def _unlock(self):
84
115
fcntl.lockf(self.f, fcntl.LOCK_UN)
88
"""Clear the self.f attribute cleanly."""
93
119
class _fcntl_WriteLock(_fcntl_FileLock):
97
123
def __init__(self, filename):
98
# standard IO errors get exposed directly.
99
self._open(filename, 'rb+')
100
self.filename = realpath(filename)
101
if self.filename in self.open_locks:
124
super(_fcntl_WriteLock, self).__init__()
125
# Check we can grab a lock before we actually open the file.
126
self.filename = osutils.realpath(filename)
127
if self.filename in _fcntl_WriteLock._open_locks:
103
raise LockContention("Lock already held.")
129
raise errors.LockContention(self.filename)
131
self._open(self.filename, 'rb+')
104
132
# reserve a slot for this lock - even if the lockf call fails,
105
133
# at thisi point unlock() will be called, because self.f is set.
106
134
# TODO: make this fully threadsafe, if we decide we care.
107
self.open_locks[self.filename] = self.filename
135
_fcntl_WriteLock._open_locks.add(self.filename)
109
137
# LOCK_NB will cause IOError to be raised if we can't grab a
110
138
# lock right away.
134
167
except IOError, e:
135
168
# we should be more precise about whats a locking
136
169
# error and whats a random-other error
170
raise errors.LockContention(e)
139
172
def unlock(self):
173
count = _fcntl_ReadLock._open_locks[self.filename]
175
del _fcntl_ReadLock._open_locks[self.filename]
177
_fcntl_ReadLock._open_locks[self.filename] = count - 1
143
WriteLock = _fcntl_WriteLock
144
ReadLock = _fcntl_ReadLock
149
import win32con, win32file, pywintypes
152
LOCK_SH = 0 # the default
153
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
154
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
156
class _w32c_FileLock(_base_Lock):
157
def _lock(self, filename, openmode, lockmode):
159
self._open(filename, openmode)
160
self.hfile = win32file._get_osfhandle(self.f.fileno())
161
overlapped = pywintypes.OVERLAPPED()
162
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
171
overlapped = pywintypes.OVERLAPPED()
172
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
179
class _w32c_ReadLock(_w32c_FileLock):
180
def __init__(self, filename):
181
_w32c_FileLock._lock(self, filename, 'rb',
184
class _w32c_WriteLock(_w32c_FileLock):
185
def __init__(self, filename):
186
_w32c_FileLock._lock(self, filename, 'rb+',
190
WriteLock = _w32c_WriteLock
191
ReadLock = _w32c_ReadLock
198
# Unfortunately, msvcrt.locking() doesn't distinguish between
199
# read locks and write locks. Also, the way the combinations
200
# work to get non-blocking is not the same, so we
201
# have to write extra special functions here.
204
class _msvc_FileLock(_base_Lock):
215
class _msvc_ReadLock(_msvc_FileLock):
216
def __init__(self, filename):
217
_msvc_lock(self._open(filename, 'rb'),
218
self.LOCK_SH | self.LOCK_NB)
221
class _msvc_WriteLock(_msvc_FileLock):
222
def __init__(self, filename):
223
_msvc_lock(self._open(filename, 'rb+'),
224
self.LOCK_EX | self.LOCK_NB)
227
def _msvc_lock(f, flags):
229
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
230
# according to the comments, LK_RLCK is open the lock for writing.
232
# Unfortunately, msvcrt.locking() also has the side effect that it
233
# will only block for 10 seconds at most, and then it will throw an
234
# exception, this isn't terrible, though.
241
fpos = os.lseek(fn, 0,0)
244
if flags & _msvc_FileLock.LOCK_SH:
245
if flags & _msvc_FileLock.LOCK_NB:
246
lock_mode = msvcrt.LK_NBLCK
248
lock_mode = msvcrt.LK_LOCK
249
elif flags & _msvc_FileLock.LOCK_EX:
250
if flags & _msvc_FileLock.LOCK_NB:
251
lock_mode = msvcrt.LK_NBRLCK
253
lock_mode = msvcrt.LK_RLCK
255
raise ValueError('Invalid lock mode: %r' % flags)
257
msvcrt.locking(fn, lock_mode, -1)
259
os.lseek(fn, fpos, 0)
271
fpos = os.lseek(fn, 0,0)
275
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
277
os.lseek(fn, fpos, 0)
282
WriteLock = _msvc_WriteLock
283
ReadLock = _msvc_ReadLock
285
raise NotImplementedError("please write a locking method "
286
"for platform %r" % sys.platform)
180
def temporary_write_lock(self):
181
"""Try to grab a write lock on the file.
183
On platforms that support it, this will upgrade to a write lock
184
without unlocking the file.
185
Otherwise, this will release the read lock, and try to acquire a
188
:return: A token which can be used to switch back to a read lock.
190
assert self.filename not in _fcntl_WriteLock._open_locks
192
wlock = _fcntl_TemporaryWriteLock(self)
193
except errors.LockError:
194
# We didn't unlock, so we can just return 'self'
199
class _fcntl_TemporaryWriteLock(_base_Lock):
200
"""A token used when grabbing a temporary_write_lock.
202
Call restore_read_lock() when you are done with the write lock.
205
def __init__(self, read_lock):
206
super(_fcntl_TemporaryWriteLock, self).__init__()
207
self._read_lock = read_lock
208
self.filename = read_lock.filename
210
count = _fcntl_ReadLock._open_locks[self.filename]
212
# Something else also has a read-lock, so we cannot grab a
214
raise errors.LockContention(self.filename)
216
assert self.filename not in _fcntl_WriteLock._open_locks
218
# See if we can open the file for writing. Another process might
219
# have a read lock. We don't use self._open() because we don't want
220
# to create the file if it exists. That would have already been
221
# done by _fcntl_ReadLock
223
new_f = open(self.filename, 'rb+')
225
if e.errno in (errno.EACCES, errno.EPERM):
226
raise errors.ReadOnlyLockError(self.filename, str(e))
229
# LOCK_NB will cause IOError to be raised if we can't grab a
231
fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
233
# TODO: Raise a more specific error based on the type of error
234
raise errors.LockContention(e)
235
_fcntl_WriteLock._open_locks.add(self.filename)
239
def restore_read_lock(self):
240
"""Restore the original ReadLock."""
241
# For fcntl, since we never released the read lock, just release the
242
# write lock, and return the original lock.
243
fcntl.lockf(self.f, fcntl.LOCK_UN)
245
_fcntl_WriteLock._open_locks.remove(self.filename)
246
# Avoid reference cycles
247
read_lock = self._read_lock
248
self._read_lock = None
252
_lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
255
if have_pywin32 and sys.platform == 'win32':
256
LOCK_SH = 0 # the default
257
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
258
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
261
class _w32c_FileLock(_base_Lock):
263
def _lock(self, filename, openmode, lockmode):
264
self._open(filename, openmode)
266
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
267
overlapped = pywintypes.OVERLAPPED()
269
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
271
except pywintypes.error, e:
273
if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
274
raise errors.LockContention(filename)
275
## import pdb; pdb.set_trace()
279
raise errors.LockContention(e)
282
overlapped = pywintypes.OVERLAPPED()
284
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
287
raise errors.LockContention(e)
290
class _w32c_ReadLock(_w32c_FileLock):
291
def __init__(self, filename):
292
super(_w32c_ReadLock, self).__init__()
293
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
295
def temporary_write_lock(self):
296
"""Try to grab a write lock on the file.
298
On platforms that support it, this will upgrade to a write lock
299
without unlocking the file.
300
Otherwise, this will release the read lock, and try to acquire a
303
:return: A token which can be used to switch back to a read lock.
305
# I can't find a way to upgrade a read lock to a write lock without
306
# unlocking first. So here, we do just that.
309
wlock = _w32c_WriteLock(self.filename)
310
except errors.LockError:
311
return False, _w32c_ReadLock(self.filename)
315
class _w32c_WriteLock(_w32c_FileLock):
316
def __init__(self, filename):
317
super(_w32c_WriteLock, self).__init__()
318
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
320
def restore_read_lock(self):
321
"""Restore the original ReadLock."""
322
# For win32 we had to completely let go of the original lock, so we
323
# just unlock and create a new read lock.
325
return _w32c_ReadLock(self.filename)
328
_lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
331
if have_ctypes and sys.platform == 'win32':
332
# These constants were copied from the win32con.py module.
333
LOCKFILE_FAIL_IMMEDIATELY = 1
334
LOCKFILE_EXCLUSIVE_LOCK = 2
335
# Constant taken from winerror.py module
336
ERROR_LOCK_VIOLATION = 33
339
LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
340
LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
341
_LockFileEx = ctypes.windll.kernel32.LockFileEx
342
_UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
343
_GetLastError = ctypes.windll.kernel32.GetLastError
345
### Define the OVERLAPPED structure.
346
# http://msdn2.microsoft.com/en-us/library/ms684342.aspx
347
# typedef struct _OVERLAPPED {
348
# ULONG_PTR Internal;
349
# ULONG_PTR InternalHigh;
360
class _inner_struct(ctypes.Structure):
361
_fields_ = [('Offset', ctypes.c_uint), # DWORD
362
('OffsetHigh', ctypes.c_uint), # DWORD
365
class _inner_union(ctypes.Union):
366
_fields_ = [('anon_struct', _inner_struct), # struct
367
('Pointer', ctypes.c_void_p), # PVOID
370
class OVERLAPPED(ctypes.Structure):
371
_fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
372
('InternalHigh', ctypes.c_void_p), # ULONG_PTR
373
('_inner_union', _inner_union),
374
('hEvent', ctypes.c_void_p), # HANDLE
377
class _ctypes_FileLock(_base_Lock):
379
def _lock(self, filename, openmode, lockmode):
380
self._open(filename, openmode)
382
self.hfile = msvcrt.get_osfhandle(self.f.fileno())
383
overlapped = OVERLAPPED()
384
result = _LockFileEx(self.hfile, # HANDLE hFile
385
lockmode, # DWORD dwFlags
386
0, # DWORD dwReserved
387
0x7fffffff, # DWORD nNumberOfBytesToLockLow
388
0x00000000, # DWORD nNumberOfBytesToLockHigh
389
ctypes.byref(overlapped), # lpOverlapped
393
last_err = _GetLastError()
394
if last_err in (ERROR_LOCK_VIOLATION,):
395
raise errors.LockContention(filename)
396
raise errors.LockContention('Unknown locking error: %s'
400
overlapped = OVERLAPPED()
401
result = _UnlockFileEx(self.hfile, # HANDLE hFile
402
0, # DWORD dwReserved
403
0x7fffffff, # DWORD nNumberOfBytesToLockLow
404
0x00000000, # DWORD nNumberOfBytesToLockHigh
405
ctypes.byref(overlapped), # lpOverlapped
410
last_err = _GetLastError()
411
raise errors.LockContention('Unknown unlocking error: %s'
415
class _ctypes_ReadLock(_ctypes_FileLock):
416
def __init__(self, filename):
417
super(_ctypes_ReadLock, self).__init__()
418
self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
420
def temporary_write_lock(self):
421
"""Try to grab a write lock on the file.
423
On platforms that support it, this will upgrade to a write lock
424
without unlocking the file.
425
Otherwise, this will release the read lock, and try to acquire a
428
:return: A token which can be used to switch back to a read lock.
430
# I can't find a way to upgrade a read lock to a write lock without
431
# unlocking first. So here, we do just that.
434
wlock = _ctypes_WriteLock(self.filename)
435
except errors.LockError:
436
return False, _ctypes_ReadLock(self.filename)
439
class _ctypes_WriteLock(_ctypes_FileLock):
440
def __init__(self, filename):
441
super(_ctypes_WriteLock, self).__init__()
442
self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
444
def restore_read_lock(self):
445
"""Restore the original ReadLock."""
446
# For win32 we had to completely let go of the original lock, so we
447
# just unlock and create a new read lock.
449
return _ctypes_ReadLock(self.filename)
452
_lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
455
if len(_lock_classes) == 0:
456
raise NotImplementedError(
457
"We must have one of fcntl, pywin32, or ctypes available"
458
" to support OS locking."
462
# We default to using the first available lock class.
463
_lock_type, WriteLock, ReadLock = _lock_classes[0]