/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: Martin Pool
  • Date: 2008-05-27 03:00:53 UTC
  • mfrom: (3452 +trunk)
  • mto: (3724.1.1 lock-hooks)
  • mto: This revision was merged to the branch mainline in revision 3730.
  • Revision ID: mbp@sourcefrog.net-20080527030053-0mct6dypek0ysjc3
merge trunk

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