1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""Locking using OS file locks or file existence.
20
Note: This method of locking is generally deprecated in favour of LockDir, but
21
is used to lock local WorkingTrees, and by some old formats. It's accessed
22
through Transport.lock_read(), etc.
24
This module causes two methods, lock() and unlock() to be defined in
25
any way that works on the current platform.
27
It is not specified whether these locks are reentrant (i.e. can be
28
taken repeatedly by a single process) or whether they exclude
29
different threads in a single process. That reentrancy is provided by
32
This defines two classes: ReadLock and WriteLock, which can be
33
implemented in different ways on different platforms. Both have an
41
from bzrlib.trace import mutter
42
from bzrlib.errors import LockError, TestPreventLocking
45
class LockWrapper(object):
46
"""A wrapper which lets us set locking ability.
48
This also lets us record what objects were locked in what order,
49
to ensure that locking happens correctly.
52
def __init__(self, sequence, other, other_id):
53
"""Wrap a locking policy around a given object.
55
:param sequence: A list object where we should record actions
56
:param other: The object to control policy on
57
:param other_id: Something to identify the object by
59
self.__dict__['_sequence'] = sequence
60
self.__dict__['_other'] = other
61
self.__dict__['_other_id'] = other_id
62
self.__dict__['_allow_write'] = True
63
self.__dict__['_allow_read'] = True
64
self.__dict__['_allow_unlock'] = True
66
def __getattr__(self, attr):
67
return getattr(self._other, attr)
69
def __setattr__(self, attr, val):
70
return setattr(self._other, attr, val)
73
self._sequence.append((self._other_id, 'lr', self._allow_read))
75
return self._other.lock_read()
76
raise TestPreventLocking('lock_read disabled')
79
self._sequence.append((self._other_id, 'lw', self._allow_write))
81
return self._other.lock_write()
82
raise TestPreventLocking('lock_write disabled')
85
self._sequence.append((self._other_id, 'ul', self._allow_unlock))
86
if self._allow_unlock:
87
return self._other.unlock()
88
raise TestPreventLocking('unlock disabled')
90
def disable_lock_read(self):
91
"""Make a lock_read call fail"""
92
self.__dict__['_allow_read'] = False
94
def disable_unlock(self):
95
"""Make an unlock call fail"""
96
self.__dict__['_allow_unlock'] = False
98
def disable_lock_write(self):
99
"""Make a lock_write call fail"""
100
self.__dict__['_allow_write'] = False
103
class _base_Lock(object):
104
def _open(self, filename, filemode):
106
self.f = open(filename, filemode)
109
if e.errno != errno.ENOENT:
112
# maybe this is an old branch (before may 2005)
113
mutter("trying to create missing branch lock %r", filename)
115
self.f = open(filename, 'wb+')
120
from warnings import warn
121
warn("lock on %r not released" % self.f)
125
raise NotImplementedError()
128
############################################################
135
class _fcntl_FileLock(_base_Lock):
139
fcntl.lockf(self.f, fcntl.LOCK_UN)
143
class _fcntl_WriteLock(_fcntl_FileLock):
144
def __init__(self, filename):
145
# standard IO errors get exposed directly.
146
self._open(filename, 'wb')
148
fcntl.lockf(self.f, fcntl.LOCK_EX)
150
# we should be more precise about whats a locking
151
# error and whats a random-other error
154
class _fcntl_ReadLock(_fcntl_FileLock):
156
def __init__(self, filename):
157
# standard IO errors get exposed directly.
158
self._open(filename, 'rb')
160
fcntl.lockf(self.f, fcntl.LOCK_SH)
162
# we should be more precise about whats a locking
163
# error and whats a random-other error
166
WriteLock = _fcntl_WriteLock
167
ReadLock = _fcntl_ReadLock
172
import win32con, win32file, pywintypes
175
LOCK_SH = 0 # the default
176
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
177
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
179
class _w32c_FileLock(_base_Lock):
180
def _lock(self, filename, openmode, lockmode):
182
self._open(filename, openmode)
183
self.hfile = win32file._get_osfhandle(self.f.fileno())
184
overlapped = pywintypes.OVERLAPPED()
185
win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
191
overlapped = pywintypes.OVERLAPPED()
192
win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
199
class _w32c_ReadLock(_w32c_FileLock):
200
def __init__(self, filename):
201
_w32c_FileLock._lock(self, filename, 'rb',
204
class _w32c_WriteLock(_w32c_FileLock):
205
def __init__(self, filename):
206
_w32c_FileLock._lock(self, filename, 'wb',
210
WriteLock = _w32c_WriteLock
211
ReadLock = _w32c_ReadLock
218
# Unfortunately, msvcrt.locking() doesn't distinguish between
219
# read locks and write locks. Also, the way the combinations
220
# work to get non-blocking is not the same, so we
221
# have to write extra special functions here.
224
class _msvc_FileLock(_base_Lock):
234
class _msvc_ReadLock(_msvc_FileLock):
235
def __init__(self, filename):
236
_msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
239
class _msvc_WriteLock(_msvc_FileLock):
240
def __init__(self, filename):
241
_msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
244
def _msvc_lock(f, flags):
246
# Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
247
# according to the comments, LK_RLCK is open the lock for writing.
249
# Unfortunately, msvcrt.locking() also has the side effect that it
250
# will only block for 10 seconds at most, and then it will throw an
251
# exception, this isn't terrible, though.
258
fpos = os.lseek(fn, 0,0)
261
if flags & _msvc_FileLock.LOCK_SH:
262
if flags & _msvc_FileLock.LOCK_NB:
263
lock_mode = msvcrt.LK_NBLCK
265
lock_mode = msvcrt.LK_LOCK
266
elif flags & _msvc_FileLock.LOCK_EX:
267
if flags & _msvc_FileLock.LOCK_NB:
268
lock_mode = msvcrt.LK_NBRLCK
270
lock_mode = msvcrt.LK_RLCK
272
raise ValueError('Invalid lock mode: %r' % flags)
274
msvcrt.locking(fn, lock_mode, -1)
276
os.lseek(fn, fpos, 0)
288
fpos = os.lseek(fn, 0,0)
292
msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
294
os.lseek(fn, fpos, 0)
299
WriteLock = _msvc_WriteLock
300
ReadLock = _msvc_ReadLock
302
raise NotImplementedError("please write a locking method "
303
"for platform %r" % sys.platform)