1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Locking using OS file locks or file existence.
19
Note: This method of locking is generally deprecated in favour of LockDir, but
20
is used to lock local WorkingTrees, and by some old formats. It's accessed
21
through Transport.lock_read(), etc.
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
This only does local locking using OS locks for now.
23
22
This module causes two methods, lock() and unlock() to be defined in
24
23
any way that works on the current platform.
26
25
It is not specified whether these locks are reentrant (i.e. can be
27
26
taken repeatedly by a single process) or whether they exclude
28
different threads in a single process. That reentrancy is provided by
27
different threads in a single process.
29
Eventually we may need to use some kind of lock representation that
30
will work on a dumb filesystem without actual locking primitives.
31
32
This defines two classes: ReadLock and WriteLock, which can be
32
33
implemented in different ways on different platforms. Both have an
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)
48
self.f = open(filename, filemode)
168
if e.errno in (errno.EACCES, errno.EPERM):
169
raise errors.LockFailed(self.filename, str(e))
170
51
if e.errno != errno.ENOENT:
173
54
# 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+')
55
mutter("trying to create missing branch lock %r" % filename)
57
self.f = open(filename, 'wb')
180
"""Clear the self.f attribute cleanly."""
63
from warnings import warn
64
warn("lock on %r not released" % self.f)
186
69
raise NotImplementedError()
194
class _fcntl_FileLock(_OSLock):
76
############################################################
83
class _fcntl_FileLock(_base_Lock):
197
87
fcntl.lockf(self.f, fcntl.LOCK_UN)
200
92
class _fcntl_WriteLock(_fcntl_FileLock):
204
93
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)
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
232
# we should be more precise about whats a locking
233
# error and whats a random-other error
234
raise errors.LockContention(self.filename, e)
95
fcntl.lockf(self._open(filename, 'wb'), fcntl.LOCK_EX)
237
_fcntl_WriteLock._open_locks.remove(self.filename)
240
100
class _fcntl_ReadLock(_fcntl_FileLock):
244
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
257
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)
263
# we should be more precise about whats a locking
264
# 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()
101
def __init__(self, filename):
103
fcntl.lockf(self._open(filename, 'rb'), fcntl.LOCK_SH)
107
WriteLock = _fcntl_WriteLock
108
ReadLock = _fcntl_ReadLock
112
import win32con, win32file, pywintypes
115
#LOCK_SH = 0 # the default
116
#LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
117
#LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
119
class _w32c_FileLock(_base_Lock):
120
def _lock(self, filename, openmode, lockmode):
122
self._open(filename, openmode)
123
self.hfile = win32file._get_osfhandle(self.f.fileno())
124
overlapped = pywintypes.OVERLAPPED()
125
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
131
overlapped = pywintypes.OVERLAPPED()
132
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
140
class _w32c_ReadLock(_w32c_FileLock):
141
def __init__(self, filename):
142
_w32c_FileLock._lock(self, filename, 'rb', 0)
144
class _w32c_WriteLock(_w32c_FileLock):
145
def __init__(self, filename):
146
_w32c_FileLock._lock(self, filename, 'wb',
147
win32con.LOCKFILE_EXCLUSIVE_LOCK)
151
WriteLock = _w32c_WriteLock
152
ReadLock = _w32c_ReadLock
159
# Unfortunately, msvcrt.locking() doesn't distinguish between
160
# read locks and write locks. Also, the way the combinations
161
# work to get non-blocking is not the same, so we
162
# have to write extra special functions here.
165
class _msvc_FileLock(_base_Lock):
175
class _msvc_ReadLock(_msvc_FileLock):
176
def __init__(self, filename):
177
_msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
180
class _msvc_WriteLock(_msvc_FileLock):
181
def __init__(self, filename):
182
_msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
186
def _msvc_lock(f, flags):
188
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
189
# according to the comments, LK_RLCK is open the lock for writing.
191
# Unfortunately, msvcrt.locking() also has the side effect that it
192
# will only block for 10 seconds at most, and then it will throw an
193
# exception, this isn't terrible, though.
200
fpos = os.lseek(fn, 0,0)
203
if flags & _msvc_FileLock.LOCK_SH:
204
if flags & _msvc_FileLock.LOCK_NB:
205
lock_mode = msvcrt.LK_NBLCK
207
lock_mode = msvcrt.LK_LOCK
208
elif flags & _msvc_FileLock.LOCK_EX:
209
if flags & _msvc_FileLock.LOCK_NB:
210
lock_mode = msvcrt.LK_NBRLCK
212
lock_mode = msvcrt.LK_RLCK
214
raise ValueError('Invalid lock mode: %r' % flags)
216
msvcrt.locking(fn, lock_mode, -1)
218
os.lseek(fn, fpos, 0)
230
fpos = os.lseek(fn, 0,0)
234
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
236
os.lseek(fn, fpos, 0)
242
WriteLock = _msvc_WriteLock
243
ReadLock = _msvc_ReadLock
245
raise NotImplementedError("please write a locking method "
246
"for platform %r" % sys.platform)