~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Jelmer Vernooij
  • Date: 2012-01-27 19:05:43 UTC
  • mto: This revision was merged to the branch mainline in revision 6450.
  • Revision ID: jelmer@samba.org-20120127190543-vk350mv4a0c7aug2
Fix weave test.

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