~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

(jelmer) Support upgrading between the 2a and development-colo formats.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
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
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
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
"""Locking using OS file locks or file existence.
35
35
"""
36
36
 
37
37
import errno
 
38
import os
38
39
import sys
 
40
import warnings
39
41
 
40
42
from bzrlib import (
 
43
    debug,
41
44
    errors,
42
45
    osutils,
43
46
    trace,
44
47
    )
 
48
from bzrlib.hooks import Hooks
 
49
from bzrlib.i18n import gettext
 
50
 
 
51
class LockHooks(Hooks):
 
52
 
 
53
    def __init__(self):
 
54
        Hooks.__init__(self, "bzrlib.lock", "Lock.hooks")
 
55
        self.add_hook('lock_acquired',
 
56
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
57
            "acquired.", (1, 8))
 
58
        self.add_hook('lock_released',
 
59
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
60
            "released.", (1, 8))
 
61
        self.add_hook('lock_broken',
 
62
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
63
            "broken.", (1, 15))
 
64
 
 
65
 
 
66
class Lock(object):
 
67
    """Base class for locks.
 
68
 
 
69
    :cvar hooks: Hook dictionary for operations on locks.
 
70
    """
 
71
 
 
72
    hooks = LockHooks()
 
73
 
 
74
 
 
75
class LockResult(object):
 
76
    """Result of an operation on a lock; passed to a hook"""
 
77
 
 
78
    def __init__(self, lock_url, details=None):
 
79
        """Create a lock result for lock with optional details about the lock."""
 
80
        self.lock_url = lock_url
 
81
        self.details = details
 
82
 
 
83
    def __eq__(self, other):
 
84
        return self.lock_url == other.lock_url and self.details == other.details
 
85
 
 
86
    def __repr__(self):
 
87
        return '%s(%s, %s)' % (self.__class__.__name__,
 
88
                             self.lock_url, self.details)
 
89
 
 
90
 
 
91
class LogicalLockResult(object):
 
92
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
 
93
 
 
94
    :ivar unlock: A callable which will unlock the lock.
 
95
    """
 
96
 
 
97
    def __init__(self, unlock):
 
98
        self.unlock = unlock
 
99
 
 
100
    def __repr__(self):
 
101
        return "LogicalLockResult(%s)" % (self.unlock)
 
102
 
 
103
 
 
104
 
 
105
def cant_unlock_not_held(locked_object):
 
106
    """An attempt to unlock failed because the object was not locked.
 
107
 
 
108
    This provides a policy point from which we can generate either a warning 
 
109
    or an exception.
 
110
    """
 
111
    # This is typically masking some other error and called from a finally
 
112
    # block, so it's useful to have the option not to generate a new error
 
113
    # here.  You can use -Werror to make it fatal.  It should possibly also
 
114
    # raise LockNotHeld.
 
115
    if 'unlock' in debug.debug_flags:
 
116
        warnings.warn("%r is already unlocked" % (locked_object,),
 
117
            stacklevel=3)
 
118
    else:
 
119
        raise errors.LockNotHeld(locked_object)
 
120
 
 
121
 
 
122
try:
 
123
    import fcntl
 
124
    have_fcntl = True
 
125
except ImportError:
 
126
    have_fcntl = False
 
127
 
 
128
have_pywin32 = False
 
129
have_ctypes_win32 = False
 
130
if sys.platform == 'win32':
 
131
    import msvcrt
 
132
    try:
 
133
        import win32file, pywintypes, winerror
 
134
        have_pywin32 = True
 
135
    except ImportError:
 
136
        pass
 
137
 
 
138
    try:
 
139
        import ctypes
 
140
        have_ctypes_win32 = True
 
141
    except ImportError:
 
142
        pass
45
143
 
46
144
 
47
145
class _OSLock(object):
73
171
            self.f.close()
74
172
            self.f = None
75
173
 
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
174
    def unlock(self):
83
175
        raise NotImplementedError()
84
176
 
85
177
 
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
178
_lock_classes = []
104
179
 
105
180
 
106
181
if have_fcntl:
107
 
    LOCK_SH = fcntl.LOCK_SH
108
 
    LOCK_NB = fcntl.LOCK_NB
109
 
    lock_EX = fcntl.LOCK_EX
110
 
 
111
182
 
112
183
    class _fcntl_FileLock(_OSLock):
113
184
 
127
198
            if self.filename in _fcntl_WriteLock._open_locks:
128
199
                self._clear_f()
129
200
                raise errors.LockContention(self.filename)
 
201
            if self.filename in _fcntl_ReadLock._open_locks:
 
202
                if 'strict_locks' in debug.debug_flags:
 
203
                    self._clear_f()
 
204
                    raise errors.LockContention(self.filename)
 
205
                else:
 
206
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
207
                                 % (self.filename,))
130
208
 
131
209
            self._open(self.filename, 'rb+')
132
210
            # reserve a slot for this lock - even if the lockf call fails,
133
 
            # at thisi point unlock() will be called, because self.f is set.
 
211
            # at this point unlock() will be called, because self.f is set.
134
212
            # TODO: make this fully threadsafe, if we decide we care.
135
213
            _fcntl_WriteLock._open_locks.add(self.filename)
136
214
            try:
143
221
                    self.unlock()
144
222
                # we should be more precise about whats a locking
145
223
                # error and whats a random-other error
146
 
                raise errors.LockContention(e)
 
224
                raise errors.LockContention(self.filename, e)
147
225
 
148
226
        def unlock(self):
149
227
            _fcntl_WriteLock._open_locks.remove(self.filename)
157
235
        def __init__(self, filename):
158
236
            super(_fcntl_ReadLock, self).__init__()
159
237
            self.filename = osutils.realpath(filename)
 
238
            if self.filename in _fcntl_WriteLock._open_locks:
 
239
                if 'strict_locks' in debug.debug_flags:
 
240
                    # We raise before calling _open so we don't need to
 
241
                    # _clear_f
 
242
                    raise errors.LockContention(self.filename)
 
243
                else:
 
244
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
245
                                 % (self.filename,))
160
246
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
161
247
            _fcntl_ReadLock._open_locks[self.filename] += 1
162
248
            self._open(filename, 'rb')
167
253
            except IOError, e:
168
254
                # we should be more precise about whats a locking
169
255
                # error and whats a random-other error
170
 
                raise errors.LockContention(e)
 
256
                raise errors.LockContention(self.filename, e)
171
257
 
172
258
        def unlock(self):
173
259
            count = _fcntl_ReadLock._open_locks[self.filename]
187
273
 
188
274
            :return: A token which can be used to switch back to a read lock.
189
275
            """
190
 
            assert self.filename not in _fcntl_WriteLock._open_locks
 
276
            if self.filename in _fcntl_WriteLock._open_locks:
 
277
                raise AssertionError('file already locked: %r'
 
278
                    % (self.filename,))
191
279
            try:
192
280
                wlock = _fcntl_TemporaryWriteLock(self)
193
281
            except errors.LockError:
213
301
                # write lock.
214
302
                raise errors.LockContention(self.filename)
215
303
 
216
 
            assert self.filename not in _fcntl_WriteLock._open_locks
 
304
            if self.filename in _fcntl_WriteLock._open_locks:
 
305
                raise AssertionError('file already locked: %r'
 
306
                    % (self.filename,))
217
307
 
218
308
            # See if we can open the file for writing. Another process might
219
309
            # have a read lock. We don't use self._open() because we don't want
231
321
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
232
322
            except IOError, e:
233
323
                # TODO: Raise a more specific error based on the type of error
234
 
                raise errors.LockContention(e)
 
324
                raise errors.LockContention(self.filename, e)
235
325
            _fcntl_WriteLock._open_locks.add(self.filename)
236
326
 
237
327
            self.f = new_f
253
343
 
254
344
 
255
345
if have_pywin32 and sys.platform == 'win32':
256
 
    LOCK_SH = 0 # the default
257
 
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
258
 
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
259
 
 
 
346
    if os.path.supports_unicode_filenames:
 
347
        # for Windows NT/2K/XP/etc
 
348
        win32file_CreateFile = win32file.CreateFileW
 
349
    else:
 
350
        # for Windows 98
 
351
        win32file_CreateFile = win32file.CreateFile
260
352
 
261
353
    class _w32c_FileLock(_OSLock):
262
354
 
263
 
        def _lock(self, filename, openmode, lockmode):
264
 
            self._open(filename, openmode)
265
 
 
266
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
267
 
            overlapped = pywintypes.OVERLAPPED()
 
355
        def _open(self, filename, access, share, cflags, pymode):
 
356
            self.filename = osutils.realpath(filename)
268
357
            try:
269
 
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
270
 
                                     overlapped)
 
358
                self._handle = win32file_CreateFile(filename, access, share,
 
359
                    None, win32file.OPEN_ALWAYS,
 
360
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
271
361
            except pywintypes.error, e:
272
 
                self._clear_f()
273
 
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
274
 
                    raise errors.LockContention(filename)
275
 
                ## import pdb; pdb.set_trace()
 
362
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
363
                    raise errors.LockFailed(filename, e)
 
364
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
365
                    raise errors.LockContention(filename, e)
276
366
                raise
277
 
            except Exception, e:
278
 
                self._clear_f()
279
 
                raise errors.LockContention(e)
 
367
            fd = win32file._open_osfhandle(self._handle, cflags)
 
368
            self.f = os.fdopen(fd, pymode)
 
369
            return self.f
280
370
 
281
371
        def unlock(self):
282
 
            overlapped = pywintypes.OVERLAPPED()
283
 
            try:
284
 
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
285
 
                self._clear_f()
286
 
            except Exception, e:
287
 
                raise errors.LockContention(e)
 
372
            self._clear_f()
 
373
            self._handle = None
288
374
 
289
375
 
290
376
    class _w32c_ReadLock(_w32c_FileLock):
291
377
        def __init__(self, filename):
292
378
            super(_w32c_ReadLock, self).__init__()
293
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
379
            self._open(filename, win32file.GENERIC_READ,
 
380
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
294
381
 
295
382
        def temporary_write_lock(self):
296
383
            """Try to grab a write lock on the file.
315
402
    class _w32c_WriteLock(_w32c_FileLock):
316
403
        def __init__(self, filename):
317
404
            super(_w32c_WriteLock, self).__init__()
318
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
405
            self._open(filename,
 
406
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
407
                os.O_RDWR, "rb+")
319
408
 
320
409
        def restore_read_lock(self):
321
410
            """Restore the original ReadLock."""
328
417
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
329
418
 
330
419
 
331
 
if have_ctypes and sys.platform == 'win32':
332
 
    # These constants were copied from the win32con.py module.
333
 
    LOCKFILE_FAIL_IMMEDIATELY = 1
334
 
    LOCKFILE_EXCLUSIVE_LOCK = 2
335
 
    # Constant taken from winerror.py module
336
 
    ERROR_LOCK_VIOLATION = 33
337
 
 
338
 
    LOCK_SH = 0
339
 
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
340
 
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
341
 
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
342
 
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
343
 
    _GetLastError = ctypes.windll.kernel32.GetLastError
344
 
 
345
 
    ### Define the OVERLAPPED structure.
346
 
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
347
 
    # typedef struct _OVERLAPPED {
348
 
    #   ULONG_PTR Internal;
349
 
    #   ULONG_PTR InternalHigh;
350
 
    #   union {
351
 
    #     struct {
352
 
    #       DWORD Offset;
353
 
    #       DWORD OffsetHigh;
354
 
    #     };
355
 
    #     PVOID Pointer;
356
 
    #   };
357
 
    #   HANDLE hEvent;
358
 
    # } OVERLAPPED,
359
 
 
360
 
    class _inner_struct(ctypes.Structure):
361
 
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
362
 
                    ('OffsetHigh', ctypes.c_uint), # DWORD
363
 
                   ]
364
 
 
365
 
    class _inner_union(ctypes.Union):
366
 
        _fields_  = [('anon_struct', _inner_struct), # struct
367
 
                     ('Pointer', ctypes.c_void_p), # PVOID
368
 
                    ]
369
 
 
370
 
    class OVERLAPPED(ctypes.Structure):
371
 
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
372
 
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
373
 
                    ('_inner_union', _inner_union),
374
 
                    ('hEvent', ctypes.c_void_p), # HANDLE
375
 
                   ]
 
420
if have_ctypes_win32:
 
421
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
422
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
423
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
424
    if os.path.supports_unicode_filenames:
 
425
        _function_name = "CreateFileW"
 
426
        LPTSTR = LPCWSTR
 
427
    else:
 
428
        _function_name = "CreateFileA"
 
429
        class LPTSTR(LPCSTR):
 
430
            def __new__(cls, obj):
 
431
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
 
432
 
 
433
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
434
    _CreateFile = ctypes.WINFUNCTYPE(
 
435
            HANDLE,                # return value
 
436
            LPTSTR,                # lpFileName
 
437
            DWORD,                 # dwDesiredAccess
 
438
            DWORD,                 # dwShareMode
 
439
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
440
            DWORD,                 # dwCreationDisposition
 
441
            DWORD,                 # dwFlagsAndAttributes
 
442
            HANDLE                 # hTemplateFile
 
443
        )((_function_name, ctypes.windll.kernel32))
 
444
 
 
445
    INVALID_HANDLE_VALUE = -1
 
446
 
 
447
    GENERIC_READ = 0x80000000
 
448
    GENERIC_WRITE = 0x40000000
 
449
    FILE_SHARE_READ = 1
 
450
    OPEN_ALWAYS = 4
 
451
    FILE_ATTRIBUTE_NORMAL = 128
 
452
 
 
453
    ERROR_ACCESS_DENIED = 5
 
454
    ERROR_SHARING_VIOLATION = 32
376
455
 
377
456
    class _ctypes_FileLock(_OSLock):
378
457
 
379
 
        def _lock(self, filename, openmode, lockmode):
380
 
            self._open(filename, openmode)
381
 
 
382
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
383
 
            overlapped = OVERLAPPED()
384
 
            result = _LockFileEx(self.hfile, # HANDLE hFile
385
 
                                 lockmode,   # DWORD dwFlags
386
 
                                 0,          # DWORD dwReserved
387
 
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
388
 
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
389
 
                                 ctypes.byref(overlapped), # lpOverlapped
390
 
                                )
391
 
            if result == 0:
392
 
                self._clear_f()
393
 
                last_err = _GetLastError()
394
 
                if last_err in (ERROR_LOCK_VIOLATION,):
395
 
                    raise errors.LockContention(filename)
396
 
                raise errors.LockContention('Unknown locking error: %s'
397
 
                                            % (last_err,))
 
458
        def _open(self, filename, access, share, cflags, pymode):
 
459
            self.filename = osutils.realpath(filename)
 
460
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
 
461
                FILE_ATTRIBUTE_NORMAL, 0)
 
462
            if handle in (INVALID_HANDLE_VALUE, 0):
 
463
                e = ctypes.WinError()
 
464
                if e.args[0] == ERROR_ACCESS_DENIED:
 
465
                    raise errors.LockFailed(filename, e)
 
466
                if e.args[0] == ERROR_SHARING_VIOLATION:
 
467
                    raise errors.LockContention(filename, e)
 
468
                raise e
 
469
            fd = msvcrt.open_osfhandle(handle, cflags)
 
470
            self.f = os.fdopen(fd, pymode)
 
471
            return self.f
398
472
 
399
473
        def unlock(self):
400
 
            overlapped = OVERLAPPED()
401
 
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
402
 
                                   0,          # DWORD dwReserved
403
 
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
404
 
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
405
 
                                   ctypes.byref(overlapped), # lpOverlapped
406
 
                                  )
407
474
            self._clear_f()
408
 
            if result == 0:
409
 
                self._clear_f()
410
 
                last_err = _GetLastError()
411
 
                raise errors.LockContention('Unknown unlocking error: %s'
412
 
                                            % (last_err,))
413
475
 
414
476
 
415
477
    class _ctypes_ReadLock(_ctypes_FileLock):
416
478
        def __init__(self, filename):
417
479
            super(_ctypes_ReadLock, self).__init__()
418
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
480
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
481
                "rb")
419
482
 
420
483
        def temporary_write_lock(self):
421
484
            """Try to grab a write lock on the file.
439
502
    class _ctypes_WriteLock(_ctypes_FileLock):
440
503
        def __init__(self, filename):
441
504
            super(_ctypes_WriteLock, self).__init__()
442
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
505
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
506
                "rb+")
443
507
 
444
508
        def restore_read_lock(self):
445
509
            """Restore the original ReadLock."""
462
526
# We default to using the first available lock class.
463
527
_lock_type, WriteLock, ReadLock = _lock_classes[0]
464
528
 
 
529
 
 
530
class _RelockDebugMixin(object):
 
531
    """Mixin support for -Drelock flag.
 
532
 
 
533
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
534
    acquiring a read- or write-lock.  If this object was previously locked (and
 
535
    locked the same way), and -Drelock is set, then this will trace.note a
 
536
    message about it.
 
537
    """
 
538
 
 
539
    _prev_lock = None
 
540
 
 
541
    def _note_lock(self, lock_type):
 
542
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
543
            if lock_type == 'r':
 
544
                type_name = 'read'
 
545
            else:
 
546
                type_name = 'write'
 
547
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
548
        self._prev_lock = lock_type
 
549