/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-14 20:15:52 UTC
  • mto: (2353.4.2 locking)
  • mto: This revision was merged to the branch mainline in revision 2360.
  • Revision ID: john@arbash-meinel.com-20070314201552-bjtfua57456dviep
Update the lock code and test code so that if more than one
lock implementation is available, they will both be tested.

It is quite a bit of overhead, for a case where we are likely to only have 1
real lock implementation per platform, but hey, for now we have 2.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
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.
7
 
 
 
7
#
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.
12
 
 
 
12
#
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
"""Locking wrappers.
 
18
"""Locking using OS file locks or file existence.
19
19
 
20
 
This only does local locking using OS locks for now.
 
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.
21
23
 
22
24
This module causes two methods, lock() and unlock() to be defined in
23
25
any way that works on the current platform.
24
26
 
25
27
It is not specified whether these locks are reentrant (i.e. can be
26
28
taken repeatedly by a single process) or whether they exclude
27
 
different threads in a single process.  
28
 
 
29
 
Eventually we may need to use some kind of lock representation that
30
 
will work on a dumb filesystem without actual locking primitives.
 
29
different threads in a single process.  That reentrancy is provided by
 
30
LockableFiles.
31
31
 
32
32
This defines two classes: ReadLock and WriteLock, which can be
33
33
implemented in different ways on different platforms.  Both have an
34
34
unlock() method.
35
35
"""
36
36
 
37
 
 
 
37
import errno
 
38
import os
38
39
import sys
39
 
import os
40
 
 
41
 
from bzrlib.trace import mutter, note, warning
42
 
from bzrlib.errors import LockError
 
40
 
 
41
from bzrlib import errors
 
42
from bzrlib.errors import LockError, LockContention
 
43
from bzrlib.osutils import realpath
 
44
from bzrlib.trace import mutter
 
45
 
43
46
 
44
47
class _base_Lock(object):
 
48
 
 
49
    def __init__(self):
 
50
        self.f = None
 
51
 
45
52
    def _open(self, filename, filemode):
46
 
        import errno
47
53
        try:
48
54
            self.f = open(filename, filemode)
49
55
            return self.f
50
56
        except IOError, e:
 
57
            if e.errno in (errno.EACCES, errno.EPERM):
 
58
                raise errors.ReadOnlyLockError(e)
51
59
            if e.errno != errno.ENOENT:
52
60
                raise
53
61
 
54
62
            # maybe this is an old branch (before may 2005)
55
 
            mutter("trying to create missing branch lock %r" % filename)
56
 
            
57
 
            self.f = open(filename, 'wb')
 
63
            mutter("trying to create missing branch lock %r", filename)
 
64
 
 
65
            self.f = open(filename, 'wb+')
58
66
            return self.f
59
67
 
 
68
    def _clear_f(self):
 
69
        """Clear the self.f attribute cleanly."""
 
70
        if self.f:
 
71
            self.f.close()
 
72
            self.f = None
60
73
 
61
74
    def __del__(self):
62
75
        if self.f:
63
76
            from warnings import warn
64
77
            warn("lock on %r not released" % self.f)
65
78
            self.unlock()
66
 
            
67
79
 
68
80
    def unlock(self):
69
81
        raise NotImplementedError()
70
82
 
71
 
        
72
 
 
73
 
 
74
 
 
75
 
 
76
 
############################################################
77
 
# msvcrt locks
78
 
 
79
 
 
 
83
 
 
84
have_ctypes = have_pywin32 = have_fcntl = False
80
85
try:
81
86
    import fcntl
 
87
    have_fcntl = True
 
88
except ImportError:
 
89
    have_fcntl = False
 
90
try:
 
91
    import win32con, win32file, pywintypes, winerror, msvcrt
 
92
    have_pywin32 = True
 
93
except ImportError:
 
94
    have_pywin32 = False
 
95
try:
 
96
    import ctypes, msvcrt
 
97
    have_ctypes = True
 
98
except ImportError:
 
99
    have_ctypes = False
 
100
 
 
101
 
 
102
_lock_classes = []
 
103
 
 
104
 
 
105
if have_fcntl:
 
106
    LOCK_SH = fcntl.LOCK_SH
 
107
    LOCK_NB = fcntl.LOCK_NB
 
108
    lock_EX = fcntl.LOCK_EX
 
109
 
82
110
 
83
111
    class _fcntl_FileLock(_base_Lock):
84
 
        f = None
85
112
 
86
 
        def unlock(self):
87
 
            fcntl.flock(self.f, fcntl.LOCK_UN)
88
 
            self.f.close()
89
 
            del self.f 
 
113
        def _unlock(self):
 
114
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
115
            self._clear_f()
90
116
 
91
117
 
92
118
    class _fcntl_WriteLock(_fcntl_FileLock):
 
119
 
 
120
        open_locks = {}
 
121
 
93
122
        def __init__(self, filename):
 
123
            # standard IO errors get exposed directly.
 
124
            super(_fcntl_WriteLock, self).__init__()
 
125
            self._open(filename, 'rb+')
 
126
            self.filename = realpath(filename)
 
127
            if self.filename in self.open_locks:
 
128
                self._clear_f()
 
129
                raise LockContention(self.filename)
 
130
            # reserve a slot for this lock - even if the lockf call fails,
 
131
            # at thisi point unlock() will be called, because self.f is set.
 
132
            # TODO: make this fully threadsafe, if we decide we care.
 
133
            self.open_locks[self.filename] = self.filename
94
134
            try:
95
 
                fcntl.flock(self._open(filename, 'wb'), fcntl.LOCK_EX)
96
 
            except Exception, e:
 
135
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
136
                # lock right away.
 
137
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
138
            except IOError, e:
 
139
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
140
                    # We couldn't grab the lock
 
141
                    self.unlock()
 
142
                # we should be more precise about whats a locking
 
143
                # error and whats a random-other error
97
144
                raise LockError(e)
98
145
 
 
146
        def unlock(self):
 
147
            del self.open_locks[self.filename]
 
148
            self._unlock()
 
149
 
99
150
 
100
151
    class _fcntl_ReadLock(_fcntl_FileLock):
101
 
        def __init__(self, filename):
102
 
            try:
103
 
                fcntl.flock(self._open(filename, 'rb'), fcntl.LOCK_SH)
104
 
            except Exception, e:
105
 
                raise LockError(e)
106
 
 
107
 
    WriteLock = _fcntl_WriteLock
108
 
    ReadLock = _fcntl_ReadLock
109
 
 
110
 
except ImportError:
111
 
    try:
112
 
        import win32con, win32file, pywintypes
113
 
 
114
 
 
115
 
        #LOCK_SH = 0 # the default
116
 
        #LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
117
 
        #LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
118
 
 
119
 
        class _w32c_FileLock(_base_Lock):
120
 
            def _lock(self, filename, openmode, lockmode):
121
 
                try:
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)
126
 
                except Exception, e:
127
 
                    raise LockError(e)
128
 
 
129
 
            def unlock(self):
130
 
                try:
131
 
                    overlapped = pywintypes.OVERLAPPED()
132
 
                    win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
133
 
                    self.f.close()
134
 
                    self.f = None
135
 
                except Exception, e:
136
 
                    raise LockError(e)
137
 
 
138
 
 
139
 
 
140
 
        class _w32c_ReadLock(_w32c_FileLock):
141
 
            def __init__(self, filename):
142
 
                _w32c_FileLock._lock(self, filename, 'rb', 0)
143
 
 
144
 
        class _w32c_WriteLock(_w32c_FileLock):
145
 
            def __init__(self, filename):
146
 
                _w32c_FileLock._lock(self, filename, 'wb',
147
 
                                     win32con.LOCKFILE_EXCLUSIVE_LOCK)
148
 
 
149
 
 
150
 
 
151
 
        WriteLock = _w32c_WriteLock
152
 
        ReadLock = _w32c_ReadLock
153
 
 
154
 
    except ImportError:
155
 
        try:
156
 
            import msvcrt
157
 
 
158
 
 
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.
163
 
 
164
 
 
165
 
            class _msvc_FileLock(_base_Lock):
166
 
                LOCK_SH = 1
167
 
                LOCK_EX = 2
168
 
                LOCK_NB = 4
169
 
                def unlock(self):
170
 
                    _msvc_unlock(self.f)
171
 
 
172
 
 
173
 
            class _msvc_ReadLock(_msvc_FileLock):
174
 
                def __init__(self, filename):
175
 
                    _msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
176
 
 
177
 
 
178
 
            class _msvc_WriteLock(_msvc_FileLock):
179
 
                def __init__(self, filename):
180
 
                    _msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
181
 
 
182
 
 
183
 
 
184
 
            def _msvc_lock(f, flags):
185
 
                try:
186
 
                    # Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
187
 
                    # according to the comments, LK_RLCK is open the lock for writing.
188
 
 
189
 
                    # Unfortunately, msvcrt.locking() also has the side effect that it
190
 
                    # will only block for 10 seconds at most, and then it will throw an
191
 
                    # exception, this isn't terrible, though.
192
 
                    if type(f) == file:
193
 
                        fpos = f.tell()
194
 
                        fn = f.fileno()
195
 
                        f.seek(0)
196
 
                    else:
197
 
                        fn = f
198
 
                        fpos = os.lseek(fn, 0,0)
199
 
                        os.lseek(fn, 0,0)
200
 
 
201
 
                    if flags & _msvc_FileLock.LOCK_SH:
202
 
                        if flags & _msvc_FileLock.LOCK_NB:
203
 
                            lock_mode = msvcrt.LK_NBLCK
204
 
                        else:
205
 
                            lock_mode = msvcrt.LK_LOCK
206
 
                    elif flags & _msvc_FileLock.LOCK_EX:
207
 
                        if flags & _msvc_FileLock.LOCK_NB:
208
 
                            lock_mode = msvcrt.LK_NBRLCK
209
 
                        else:
210
 
                            lock_mode = msvcrt.LK_RLCK
211
 
                    else:
212
 
                        raise ValueError('Invalid lock mode: %r' % flags)
213
 
                    try:
214
 
                        msvcrt.locking(fn, lock_mode, -1)
215
 
                    finally:
216
 
                        os.lseek(fn, fpos, 0)
217
 
                except Exception, e:
218
 
                    raise LockError(e)
219
 
 
220
 
            def _msvc_unlock(f):
221
 
                try:
222
 
                    if type(f) == file:
223
 
                        fpos = f.tell()
224
 
                        fn = f.fileno()
225
 
                        f.seek(0)
226
 
                    else:
227
 
                        fn = f
228
 
                        fpos = os.lseek(fn, 0,0)
229
 
                        os.lseek(fn, 0,0)
230
 
 
231
 
                    try:
232
 
                        msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
233
 
                    finally:
234
 
                        os.lseek(fn, fpos, 0)
235
 
                except Exception, e:
236
 
                    raise LockError(e)
237
 
 
238
 
 
239
 
 
240
 
            WriteLock = _msvc_WriteLock
241
 
            ReadLock = _msvc_ReadLock
242
 
        except ImportError:
243
 
            raise NotImplementedError("please write a locking method "
244
 
                                      "for platform %r" % sys.platform)
245
 
 
246
 
 
247
 
 
248
 
 
249
 
 
250
 
 
251
 
 
 
152
 
 
153
        open_locks = {}
 
154
 
 
155
        def __init__(self, filename):
 
156
            super(_fcntl_ReadLock, self).__init__()
 
157
            self._open(filename, 'rb')
 
158
            try:
 
159
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
160
                # lock right away.
 
161
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
162
            except IOError, e:
 
163
                # we should be more precise about whats a locking
 
164
                # error and whats a random-other error
 
165
                raise LockError(e)
 
166
 
 
167
        def unlock(self):
 
168
            self._unlock()
 
169
 
 
170
 
 
171
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
172
 
 
173
if have_pywin32:
 
174
    LOCK_SH = 0 # the default
 
175
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
176
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
177
 
 
178
 
 
179
    class _w32c_FileLock(_base_Lock):
 
180
 
 
181
        def _lock(self, filename, openmode, lockmode):
 
182
            self._open(filename, openmode)
 
183
 
 
184
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
185
            overlapped = pywintypes.OVERLAPPED()
 
186
            try:
 
187
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
188
                                     overlapped)
 
189
            except pywintypes.error, e:
 
190
                self._clear_f()
 
191
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
 
192
                    raise errors.LockContention(filename)
 
193
                ## import pdb; pdb.set_trace()
 
194
                raise
 
195
            except Exception, e:
 
196
                self._clear_f()
 
197
                raise LockError(e)
 
198
 
 
199
        def unlock(self):
 
200
            overlapped = pywintypes.OVERLAPPED()
 
201
            try:
 
202
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
203
                self._clear_f()
 
204
            except Exception, e:
 
205
                raise LockError(e)
 
206
 
 
207
 
 
208
    class _w32c_ReadLock(_w32c_FileLock):
 
209
        def __init__(self, filename):
 
210
            super(_w32c_ReadLock, self).__init__()
 
211
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
212
 
 
213
    class _w32c_WriteLock(_w32c_FileLock):
 
214
        def __init__(self, filename):
 
215
            super(_w32c_WriteLock, self).__init__()
 
216
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
217
 
 
218
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
219
 
 
220
if have_ctypes:
 
221
    # These constants were copied from the win32con.py module.
 
222
    LOCKFILE_FAIL_IMMEDIATELY = 1
 
223
    LOCKFILE_EXCLUSIVE_LOCK = 2
 
224
    # Constant taken from winerror.py module
 
225
    ERROR_LOCK_VIOLATION = 33
 
226
 
 
227
    LOCK_SH = 0
 
228
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
 
229
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
 
230
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
 
231
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
 
232
    _GetLastError = ctypes.windll.kernel32.GetLastError
 
233
 
 
234
    ### Define the OVERLAPPED structure.
 
235
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
 
236
    # typedef struct _OVERLAPPED {
 
237
    #   ULONG_PTR Internal;
 
238
    #   ULONG_PTR InternalHigh;
 
239
    #   union {
 
240
    #     struct {
 
241
    #       DWORD Offset;
 
242
    #       DWORD OffsetHigh;
 
243
    #     };
 
244
    #     PVOID Pointer;
 
245
    #   };
 
246
    #   HANDLE hEvent;
 
247
    # } OVERLAPPED,
 
248
 
 
249
    class _inner_struct(ctypes.Structure):
 
250
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
 
251
                    ('OffsetHigh', ctypes.c_uint), # DWORD
 
252
                   ]
 
253
 
 
254
    class _inner_union(ctypes.Union):
 
255
        _fields_  = [('anon_struct', _inner_struct), # struct
 
256
                     ('Pointer', ctypes.c_void_p), # PVOID
 
257
                    ]
 
258
 
 
259
    class OVERLAPPED(ctypes.Structure):
 
260
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
 
261
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
 
262
                    ('_inner_union', _inner_union),
 
263
                    ('hEvent', ctypes.c_void_p), # HANDLE
 
264
                   ]
 
265
 
 
266
    class _ctypes_FileLock(_base_Lock):
 
267
 
 
268
        def _lock(self, filename, openmode, lockmode):
 
269
            self._open(filename, openmode)
 
270
 
 
271
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
272
            overlapped = OVERLAPPED()
 
273
            p_overlapped = ctypes.pointer(overlapped)
 
274
            result = _LockFileEx(self.hfile, # HANDLE hFile
 
275
                                 lockmode,   # DWORD dwFlags
 
276
                                 0,          # DWORD dwReserved
 
277
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
278
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
 
279
                                 p_overlapped, # lpOverlapped
 
280
                                )
 
281
            if result == 0:
 
282
                self._clear_f()
 
283
                last_err = _GetLastError()
 
284
                if last_err in (ERROR_LOCK_VIOLATION,):
 
285
                    raise errors.LockContention(filename)
 
286
                raise errors.LockError('Unknown locking error: %s'
 
287
                                       % (last_err,))
 
288
 
 
289
        def unlock(self):
 
290
            overlapped = OVERLAPPED()
 
291
            p_overlapped = ctypes.pointer(overlapped)
 
292
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
 
293
                                   0,          # DWORD dwReserved
 
294
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
295
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
 
296
                                   p_overlapped, # lpOverlapped
 
297
                                  )
 
298
            self._clear_f()
 
299
            if result == 0:
 
300
                self._clear_f()
 
301
                last_err = _GetLastError()
 
302
                raise errors.LockError('Unknown unlocking error: %s'
 
303
                                       % (last_err,))
 
304
 
 
305
 
 
306
    class _ctypes_ReadLock(_ctypes_FileLock):
 
307
        def __init__(self, filename):
 
308
            super(_ctypes_ReadLock, self).__init__()
 
309
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
310
 
 
311
    class _ctypes_WriteLock(_ctypes_FileLock):
 
312
        def __init__(self, filename):
 
313
            super(_ctypes_WriteLock, self).__init__()
 
314
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
315
 
 
316
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
317
 
 
318
 
 
319
if len(_lock_classes) == 0:
 
320
    raise NotImplementedError("We only have support for"
 
321
                              " fcntl, pywin32 or ctypes locking."
 
322
                              " If your platform (windows) does not"
 
323
                              " support fcntl locks, you must have"
 
324
                              " either pywin32 or ctypes installed.")
 
325
 
 
326
# We default to using the first available lock class.
 
327
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
328
 
 
329
 
 
330
class LockTreeTestProviderAdapter(object):
 
331
    """A tool to generate a suite testing multiple lock formats at once.
 
332
 
 
333
    This is done by copying the test once for each lock and injecting the
 
334
    read_lock and write_lock classes.
 
335
    They are also given a new test id.
 
336
    """
 
337
 
 
338
    def __init__(self, lock_classes):
 
339
        self._lock_classes = lock_classes
 
340
 
 
341
    def _clone_test(self, test, write_lock, read_lock, variation):
 
342
        """Clone test for adaption."""
 
343
        new_test = deepcopy(test)
 
344
        new_test.write_lock = write_lock
 
345
        new_test.read_lock = read_lock
 
346
        def make_new_test_id():
 
347
            new_id = "%s(%s)" % (test.id(), variation)
 
348
            return lambda: new_id
 
349
        new_test.id = make_new_test_id()
 
350
        return new_test
 
351
 
 
352
    def adapt(self, test):
 
353
        from bzrlib.tests import TestSuite
 
354
        result = TestSuite()
 
355
        for name, write_lock, read_lock in self._lock_classes:
 
356
            new_test = self._clone_test(test, write_lock, read_lock, name)
 
357
            result.addTest(new_test)
 
358
        return result