/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

[merge] bzr.dev 2359

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
 
 
18
"""Locking using OS file locks or file existence.
 
19
 
 
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.
 
23
 
 
24
This module causes two methods, lock() and unlock() to be defined in
 
25
any way that works on the current platform.
 
26
 
 
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
 
30
LockableFiles.
 
31
 
 
32
This defines two classes: ReadLock and WriteLock, which can be
 
33
implemented in different ways on different platforms.  Both have an
 
34
unlock() method.
 
35
"""
 
36
 
 
37
import errno
 
38
import sys
 
39
 
 
40
from bzrlib import (
 
41
    errors,
 
42
    osutils,
 
43
    trace,
 
44
    )
 
45
 
 
46
 
 
47
class _base_Lock(object):
 
48
 
 
49
    def __init__(self):
 
50
        self.f = None
 
51
        self.filename = None
 
52
 
 
53
    def _open(self, filename, filemode):
 
54
        self.filename = osutils.realpath(filename)
 
55
        try:
 
56
            self.f = open(self.filename, filemode)
 
57
            return self.f
 
58
        except IOError, e:
 
59
            if e.errno in (errno.EACCES, errno.EPERM):
 
60
                raise errors.ReadOnlyLockError(self.filename, str(e))
 
61
            if e.errno != errno.ENOENT:
 
62
                raise
 
63
 
 
64
            # maybe this is an old branch (before may 2005)
 
65
            trace.mutter("trying to create missing lock %r", self.filename)
 
66
 
 
67
            self.f = open(self.filename, 'wb+')
 
68
            return self.f
 
69
 
 
70
    def _clear_f(self):
 
71
        """Clear the self.f attribute cleanly."""
 
72
        if self.f:
 
73
            self.f.close()
 
74
            self.f = None
 
75
 
 
76
    def __del__(self):
 
77
        if self.f:
 
78
            from warnings import warn
 
79
            warn("lock on %r not released" % self.f)
 
80
            self.unlock()
 
81
 
 
82
    def unlock(self):
 
83
        raise NotImplementedError()
 
84
 
 
85
 
 
86
try:
 
87
    import fcntl
 
88
    have_fcntl = True
 
89
except ImportError:
 
90
    have_fcntl = False
 
91
try:
 
92
    import win32con, win32file, pywintypes, winerror, msvcrt
 
93
    have_pywin32 = True
 
94
except ImportError:
 
95
    have_pywin32 = False
 
96
try:
 
97
    import ctypes, msvcrt
 
98
    have_ctypes = True
 
99
except ImportError:
 
100
    have_ctypes = False
 
101
 
 
102
 
 
103
_lock_classes = []
 
104
 
 
105
 
 
106
if have_fcntl:
 
107
    LOCK_SH = fcntl.LOCK_SH
 
108
    LOCK_NB = fcntl.LOCK_NB
 
109
    lock_EX = fcntl.LOCK_EX
 
110
 
 
111
 
 
112
    class _fcntl_FileLock(_base_Lock):
 
113
 
 
114
        def _unlock(self):
 
115
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
116
            self._clear_f()
 
117
 
 
118
 
 
119
    class _fcntl_WriteLock(_fcntl_FileLock):
 
120
 
 
121
        _open_locks = set()
 
122
 
 
123
        def __init__(self, filename):
 
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
 
128
                or self.filename in _fcntl_ReadLock._open_locks):
 
129
                self._clear_f()
 
130
                raise errors.LockContention(self.filename)
 
131
 
 
132
            self._open(self.filename, 'rb+')
 
133
            # reserve a slot for this lock - even if the lockf call fails,
 
134
            # at thisi point unlock() will be called, because self.f is set.
 
135
            # TODO: make this fully threadsafe, if we decide we care.
 
136
            _fcntl_WriteLock._open_locks.add(self.filename)
 
137
            try:
 
138
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
139
                # lock right away.
 
140
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
141
            except IOError, e:
 
142
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
143
                    # We couldn't grab the lock
 
144
                    self.unlock()
 
145
                # we should be more precise about whats a locking
 
146
                # error and whats a random-other error
 
147
                raise errors.LockError(e)
 
148
 
 
149
        def unlock(self):
 
150
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
151
            self._unlock()
 
152
 
 
153
 
 
154
    class _fcntl_ReadLock(_fcntl_FileLock):
 
155
 
 
156
        _open_locks = {}
 
157
 
 
158
        def __init__(self, filename, _ignore_write_lock=False):
 
159
            super(_fcntl_ReadLock, self).__init__()
 
160
            self.filename = osutils.realpath(filename)
 
161
            if not _ignore_write_lock and self.filename in _fcntl_WriteLock._open_locks:
 
162
                raise errors.LockContention(self.filename)
 
163
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
164
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
165
            self._open(filename, 'rb')
 
166
            try:
 
167
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
168
                # lock right away.
 
169
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
170
            except IOError, e:
 
171
                # we should be more precise about whats a locking
 
172
                # error and whats a random-other error
 
173
                raise errors.LockError(e)
 
174
 
 
175
        def unlock(self):
 
176
            count = _fcntl_ReadLock._open_locks[self.filename]
 
177
            if count == 1:
 
178
                del _fcntl_ReadLock._open_locks[self.filename]
 
179
            else:
 
180
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
181
            self._unlock()
 
182
 
 
183
        def temporary_write_lock(self):
 
184
            """Try to grab a write lock on the file.
 
185
 
 
186
            On platforms that support it, this will upgrade to a write lock
 
187
            without unlocking the file.
 
188
            Otherwise, this will release the read lock, and try to acquire a
 
189
            write lock.
 
190
 
 
191
            :return: A token which can be used to switch back to a read lock.
 
192
            """
 
193
            assert self.filename not in _fcntl_WriteLock._open_locks
 
194
            try:
 
195
                wlock = _fcntl_TemporaryWriteLock(self)
 
196
            except errors.LockError:
 
197
                # We didn't unlock, so we can just return 'self'
 
198
                return False, self
 
199
            return True, wlock
 
200
 
 
201
 
 
202
    class _fcntl_TemporaryWriteLock(_base_Lock):
 
203
        """A token used when grabbing a temporary_write_lock.
 
204
 
 
205
        Call restore_read_lock() when you are done with the write lock.
 
206
        """
 
207
 
 
208
        def __init__(self, read_lock):
 
209
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
210
            self._read_lock = read_lock
 
211
            self.filename = read_lock.filename
 
212
 
 
213
            count = _fcntl_ReadLock._open_locks[self.filename]
 
214
            if count > 1:
 
215
                # Something else also has a read-lock, so we cannot grab a
 
216
                # write lock.
 
217
                raise errors.LockContention(self.filename)
 
218
 
 
219
            assert self.filename not in _fcntl_WriteLock._open_locks
 
220
 
 
221
            # See if we can open the file for writing. Another process might
 
222
            # have a read lock. We don't use self._open() because we don't want
 
223
            # to create the file if it exists. That would have already been
 
224
            # done by _fcntl_ReadLock
 
225
            try:
 
226
                new_f = open(self.filename, 'rb+')
 
227
            except IOError, e:
 
228
                if e.errno in (errno.EACCES, errno.EPERM):
 
229
                    raise errors.ReadOnlyLockError(self.filename, str(e))
 
230
                raise
 
231
            try:
 
232
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
233
                # lock right away.
 
234
                fcntl.lockf(new_f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
235
            except IOError, e:
 
236
                # TODO: Raise a more specific error based on the type of error
 
237
                raise errors.LockError(e)
 
238
            _fcntl_WriteLock._open_locks.add(self.filename)
 
239
 
 
240
            self.f = new_f
 
241
 
 
242
        def restore_read_lock(self):
 
243
            """Restore the original ReadLock."""
 
244
            # For fcntl, since we never released the read lock, just release the
 
245
            # write lock, and return the original lock.
 
246
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
247
            self._clear_f()
 
248
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
249
            # Avoid reference cycles
 
250
            read_lock = self._read_lock
 
251
            self._read_lock = None
 
252
            return read_lock
 
253
 
 
254
 
 
255
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
256
 
 
257
 
 
258
if have_pywin32 and sys.platform == 'win32':
 
259
    LOCK_SH = 0 # the default
 
260
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
261
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
262
 
 
263
 
 
264
    class _w32c_FileLock(_base_Lock):
 
265
 
 
266
        def _lock(self, filename, openmode, lockmode):
 
267
            self._open(filename, openmode)
 
268
 
 
269
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
270
            overlapped = pywintypes.OVERLAPPED()
 
271
            try:
 
272
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
273
                                     overlapped)
 
274
            except pywintypes.error, e:
 
275
                self._clear_f()
 
276
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
 
277
                    raise errors.LockContention(filename)
 
278
                ## import pdb; pdb.set_trace()
 
279
                raise
 
280
            except Exception, e:
 
281
                self._clear_f()
 
282
                raise errors.LockError(e)
 
283
 
 
284
        def unlock(self):
 
285
            overlapped = pywintypes.OVERLAPPED()
 
286
            try:
 
287
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
288
                self._clear_f()
 
289
            except Exception, e:
 
290
                raise errors.LockError(e)
 
291
 
 
292
 
 
293
    class _w32c_ReadLock(_w32c_FileLock):
 
294
        def __init__(self, filename):
 
295
            super(_w32c_ReadLock, self).__init__()
 
296
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
297
 
 
298
        def temporary_write_lock(self):
 
299
            """Try to grab a write lock on the file.
 
300
 
 
301
            On platforms that support it, this will upgrade to a write lock
 
302
            without unlocking the file.
 
303
            Otherwise, this will release the read lock, and try to acquire a
 
304
            write lock.
 
305
 
 
306
            :return: A token which can be used to switch back to a read lock.
 
307
            """
 
308
            # I can't find a way to upgrade a read lock to a write lock without
 
309
            # unlocking first. So here, we do just that.
 
310
            self.unlock()
 
311
            try:
 
312
                wlock = _w32c_WriteLock(self.filename)
 
313
            except errors.LockError:
 
314
                return False, _w32c_ReadLock(self.filename)
 
315
            return True, wlock
 
316
 
 
317
 
 
318
    class _w32c_WriteLock(_w32c_FileLock):
 
319
        def __init__(self, filename):
 
320
            super(_w32c_WriteLock, self).__init__()
 
321
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
322
 
 
323
        def restore_read_lock(self):
 
324
            """Restore the original ReadLock."""
 
325
            # For win32 we had to completely let go of the original lock, so we
 
326
            # just unlock and create a new read lock.
 
327
            self.unlock()
 
328
            return _w32c_ReadLock(self.filename)
 
329
 
 
330
 
 
331
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
332
 
 
333
 
 
334
if have_ctypes and sys.platform == 'win32':
 
335
    # These constants were copied from the win32con.py module.
 
336
    LOCKFILE_FAIL_IMMEDIATELY = 1
 
337
    LOCKFILE_EXCLUSIVE_LOCK = 2
 
338
    # Constant taken from winerror.py module
 
339
    ERROR_LOCK_VIOLATION = 33
 
340
 
 
341
    LOCK_SH = 0
 
342
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
 
343
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
 
344
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
 
345
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
 
346
    _GetLastError = ctypes.windll.kernel32.GetLastError
 
347
 
 
348
    ### Define the OVERLAPPED structure.
 
349
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
 
350
    # typedef struct _OVERLAPPED {
 
351
    #   ULONG_PTR Internal;
 
352
    #   ULONG_PTR InternalHigh;
 
353
    #   union {
 
354
    #     struct {
 
355
    #       DWORD Offset;
 
356
    #       DWORD OffsetHigh;
 
357
    #     };
 
358
    #     PVOID Pointer;
 
359
    #   };
 
360
    #   HANDLE hEvent;
 
361
    # } OVERLAPPED,
 
362
 
 
363
    class _inner_struct(ctypes.Structure):
 
364
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
 
365
                    ('OffsetHigh', ctypes.c_uint), # DWORD
 
366
                   ]
 
367
 
 
368
    class _inner_union(ctypes.Union):
 
369
        _fields_  = [('anon_struct', _inner_struct), # struct
 
370
                     ('Pointer', ctypes.c_void_p), # PVOID
 
371
                    ]
 
372
 
 
373
    class OVERLAPPED(ctypes.Structure):
 
374
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
 
375
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
 
376
                    ('_inner_union', _inner_union),
 
377
                    ('hEvent', ctypes.c_void_p), # HANDLE
 
378
                   ]
 
379
 
 
380
    class _ctypes_FileLock(_base_Lock):
 
381
 
 
382
        def _lock(self, filename, openmode, lockmode):
 
383
            self._open(filename, openmode)
 
384
 
 
385
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
386
            overlapped = OVERLAPPED()
 
387
            result = _LockFileEx(self.hfile, # HANDLE hFile
 
388
                                 lockmode,   # DWORD dwFlags
 
389
                                 0,          # DWORD dwReserved
 
390
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
391
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
 
392
                                 ctypes.byref(overlapped), # lpOverlapped
 
393
                                )
 
394
            if result == 0:
 
395
                self._clear_f()
 
396
                last_err = _GetLastError()
 
397
                if last_err in (ERROR_LOCK_VIOLATION,):
 
398
                    raise errors.LockContention(filename)
 
399
                raise errors.LockError('Unknown locking error: %s'
 
400
                                       % (last_err,))
 
401
 
 
402
        def unlock(self):
 
403
            overlapped = OVERLAPPED()
 
404
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
 
405
                                   0,          # DWORD dwReserved
 
406
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
407
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
 
408
                                   ctypes.byref(overlapped), # lpOverlapped
 
409
                                  )
 
410
            self._clear_f()
 
411
            if result == 0:
 
412
                self._clear_f()
 
413
                last_err = _GetLastError()
 
414
                raise errors.LockError('Unknown unlocking error: %s'
 
415
                                       % (last_err,))
 
416
 
 
417
 
 
418
    class _ctypes_ReadLock(_ctypes_FileLock):
 
419
        def __init__(self, filename):
 
420
            super(_ctypes_ReadLock, self).__init__()
 
421
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
422
 
 
423
        def temporary_write_lock(self):
 
424
            """Try to grab a write lock on the file.
 
425
 
 
426
            On platforms that support it, this will upgrade to a write lock
 
427
            without unlocking the file.
 
428
            Otherwise, this will release the read lock, and try to acquire a
 
429
            write lock.
 
430
 
 
431
            :return: A token which can be used to switch back to a read lock.
 
432
            """
 
433
            # I can't find a way to upgrade a read lock to a write lock without
 
434
            # unlocking first. So here, we do just that.
 
435
            self.unlock()
 
436
            try:
 
437
                wlock = _ctypes_WriteLock(self.filename)
 
438
            except errors.LockError:
 
439
                return False, _ctypes_ReadLock(self.filename)
 
440
            return True, wlock
 
441
 
 
442
    class _ctypes_WriteLock(_ctypes_FileLock):
 
443
        def __init__(self, filename):
 
444
            super(_ctypes_WriteLock, self).__init__()
 
445
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
446
 
 
447
        def restore_read_lock(self):
 
448
            """Restore the original ReadLock."""
 
449
            # For win32 we had to completely let go of the original lock, so we
 
450
            # just unlock and create a new read lock.
 
451
            self.unlock()
 
452
            return _ctypes_ReadLock(self.filename)
 
453
 
 
454
 
 
455
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
456
 
 
457
 
 
458
if len(_lock_classes) == 0:
 
459
    raise NotImplementedError(
 
460
        "We must have one of fcntl, pywin32, or ctypes available"
 
461
        " to support OS locking."
 
462
        )
 
463
 
 
464
 
 
465
# We default to using the first available lock class.
 
466
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
467