~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Andrew Bennetts
  • Date: 2010-06-25 06:47:40 UTC
  • mto: (5050.3.16 2.2)
  • mto: This revision was merged to the branch mainline in revision 5365.
  • Revision ID: andrew.bennetts@canonical.com-20100625064740-k93ngat248kdcqdm
Remove merge_into_helper for now, as it currently has no callers.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 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
    )
45
 
from bzrlib.hooks import Hooks
 
48
from bzrlib.hooks import HookPoint, Hooks
46
49
 
47
50
 
48
51
class LockHooks(Hooks):
49
52
 
50
53
    def __init__(self):
51
54
        Hooks.__init__(self)
52
 
 
53
 
        # added in 1.8; called with a LockResult when a physical lock is
54
 
        # acquired
55
 
        self['lock_acquired'] = []
56
 
 
57
 
        # added in 1.8; called with a LockResult when a physical lock is
58
 
        # acquired
59
 
        self['lock_released'] = []
 
55
        self.create_hook(HookPoint('lock_acquired',
 
56
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
57
            "acquired.", (1, 8), None))
 
58
        self.create_hook(HookPoint('lock_released',
 
59
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
60
            "released.", (1, 8), None))
 
61
        self.create_hook(HookPoint('lock_broken',
 
62
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
63
            "broken.", (1, 15), None))
60
64
 
61
65
 
62
66
class Lock(object):
79
83
    def __eq__(self, other):
80
84
        return self.lock_url == other.lock_url and self.details == other.details
81
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
 
82
121
 
83
122
try:
84
123
    import fcntl
91
130
if sys.platform == 'win32':
92
131
    import msvcrt
93
132
    try:
94
 
        import win32con, win32file, pywintypes, winerror
 
133
        import win32file, pywintypes, winerror
95
134
        have_pywin32 = True
96
135
    except ImportError:
97
136
        pass
146
185
 
147
186
 
148
187
if have_fcntl:
149
 
    LOCK_SH = fcntl.LOCK_SH
150
 
    LOCK_NB = fcntl.LOCK_NB
151
 
    lock_EX = fcntl.LOCK_EX
152
 
 
153
188
 
154
189
    class _fcntl_FileLock(_OSLock):
155
190
 
169
204
            if self.filename in _fcntl_WriteLock._open_locks:
170
205
                self._clear_f()
171
206
                raise errors.LockContention(self.filename)
 
207
            if self.filename in _fcntl_ReadLock._open_locks:
 
208
                if 'strict_locks' in debug.debug_flags:
 
209
                    self._clear_f()
 
210
                    raise errors.LockContention(self.filename)
 
211
                else:
 
212
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
213
                                 % (self.filename,))
172
214
 
173
215
            self._open(self.filename, 'rb+')
174
216
            # reserve a slot for this lock - even if the lockf call fails,
175
 
            # at thisi point unlock() will be called, because self.f is set.
 
217
            # at this point unlock() will be called, because self.f is set.
176
218
            # TODO: make this fully threadsafe, if we decide we care.
177
219
            _fcntl_WriteLock._open_locks.add(self.filename)
178
220
            try:
185
227
                    self.unlock()
186
228
                # we should be more precise about whats a locking
187
229
                # error and whats a random-other error
188
 
                raise errors.LockContention(e)
 
230
                raise errors.LockContention(self.filename, e)
189
231
 
190
232
        def unlock(self):
191
233
            _fcntl_WriteLock._open_locks.remove(self.filename)
199
241
        def __init__(self, filename):
200
242
            super(_fcntl_ReadLock, self).__init__()
201
243
            self.filename = osutils.realpath(filename)
 
244
            if self.filename in _fcntl_WriteLock._open_locks:
 
245
                if 'strict_locks' in debug.debug_flags:
 
246
                    # We raise before calling _open so we don't need to
 
247
                    # _clear_f
 
248
                    raise errors.LockContention(self.filename)
 
249
                else:
 
250
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
251
                                 % (self.filename,))
202
252
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
203
253
            _fcntl_ReadLock._open_locks[self.filename] += 1
204
254
            self._open(filename, 'rb')
209
259
            except IOError, e:
210
260
                # we should be more precise about whats a locking
211
261
                # error and whats a random-other error
212
 
                raise errors.LockContention(e)
 
262
                raise errors.LockContention(self.filename, e)
213
263
 
214
264
        def unlock(self):
215
265
            count = _fcntl_ReadLock._open_locks[self.filename]
277
327
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
278
328
            except IOError, e:
279
329
                # TODO: Raise a more specific error based on the type of error
280
 
                raise errors.LockContention(e)
 
330
                raise errors.LockContention(self.filename, e)
281
331
            _fcntl_WriteLock._open_locks.add(self.filename)
282
332
 
283
333
            self.f = new_f
299
349
 
300
350
 
301
351
if have_pywin32 and sys.platform == 'win32':
302
 
    LOCK_SH = 0 # the default
303
 
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
304
 
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
305
 
 
 
352
    if os.path.supports_unicode_filenames:
 
353
        # for Windows NT/2K/XP/etc
 
354
        win32file_CreateFile = win32file.CreateFileW
 
355
    else:
 
356
        # for Windows 98
 
357
        win32file_CreateFile = win32file.CreateFile
306
358
 
307
359
    class _w32c_FileLock(_OSLock):
308
360
 
309
 
        def _lock(self, filename, openmode, lockmode):
310
 
            self._open(filename, openmode)
311
 
 
312
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
313
 
            overlapped = pywintypes.OVERLAPPED()
 
361
        def _open(self, filename, access, share, cflags, pymode):
 
362
            self.filename = osutils.realpath(filename)
314
363
            try:
315
 
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
316
 
                                     overlapped)
 
364
                self._handle = win32file_CreateFile(filename, access, share,
 
365
                    None, win32file.OPEN_ALWAYS,
 
366
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
317
367
            except pywintypes.error, e:
318
 
                self._clear_f()
319
 
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
320
 
                    raise errors.LockContention(filename)
321
 
                ## import pdb; pdb.set_trace()
 
368
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
369
                    raise errors.LockFailed(filename, e)
 
370
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
371
                    raise errors.LockContention(filename, e)
322
372
                raise
323
 
            except Exception, e:
324
 
                self._clear_f()
325
 
                raise errors.LockContention(e)
 
373
            fd = win32file._open_osfhandle(self._handle, cflags)
 
374
            self.f = os.fdopen(fd, pymode)
 
375
            return self.f
326
376
 
327
377
        def unlock(self):
328
 
            overlapped = pywintypes.OVERLAPPED()
329
 
            try:
330
 
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
331
 
                self._clear_f()
332
 
            except Exception, e:
333
 
                raise errors.LockContention(e)
 
378
            self._clear_f()
 
379
            self._handle = None
334
380
 
335
381
 
336
382
    class _w32c_ReadLock(_w32c_FileLock):
337
383
        def __init__(self, filename):
338
384
            super(_w32c_ReadLock, self).__init__()
339
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
385
            self._open(filename, win32file.GENERIC_READ,
 
386
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
340
387
 
341
388
        def temporary_write_lock(self):
342
389
            """Try to grab a write lock on the file.
361
408
    class _w32c_WriteLock(_w32c_FileLock):
362
409
        def __init__(self, filename):
363
410
            super(_w32c_WriteLock, self).__init__()
364
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
411
            self._open(filename,
 
412
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
413
                os.O_RDWR, "rb+")
365
414
 
366
415
        def restore_read_lock(self):
367
416
            """Restore the original ReadLock."""
375
424
 
376
425
 
377
426
if have_ctypes_win32:
378
 
    # These constants were copied from the win32con.py module.
379
 
    LOCKFILE_FAIL_IMMEDIATELY = 1
380
 
    LOCKFILE_EXCLUSIVE_LOCK = 2
381
 
    # Constant taken from winerror.py module
382
 
    ERROR_LOCK_VIOLATION = 33
383
 
 
384
 
    LOCK_SH = 0
385
 
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
386
 
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
387
 
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
388
 
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
389
 
    _GetLastError = ctypes.windll.kernel32.GetLastError
390
 
 
391
 
    ### Define the OVERLAPPED structure.
392
 
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
393
 
    # typedef struct _OVERLAPPED {
394
 
    #   ULONG_PTR Internal;
395
 
    #   ULONG_PTR InternalHigh;
396
 
    #   union {
397
 
    #     struct {
398
 
    #       DWORD Offset;
399
 
    #       DWORD OffsetHigh;
400
 
    #     };
401
 
    #     PVOID Pointer;
402
 
    #   };
403
 
    #   HANDLE hEvent;
404
 
    # } OVERLAPPED,
405
 
 
406
 
    class _inner_struct(ctypes.Structure):
407
 
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
408
 
                    ('OffsetHigh', ctypes.c_uint), # DWORD
409
 
                   ]
410
 
 
411
 
    class _inner_union(ctypes.Union):
412
 
        _fields_  = [('anon_struct', _inner_struct), # struct
413
 
                     ('Pointer', ctypes.c_void_p), # PVOID
414
 
                    ]
415
 
 
416
 
    class OVERLAPPED(ctypes.Structure):
417
 
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
418
 
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
419
 
                    ('_inner_union', _inner_union),
420
 
                    ('hEvent', ctypes.c_void_p), # HANDLE
421
 
                   ]
 
427
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
428
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
429
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
430
    if os.path.supports_unicode_filenames:
 
431
        _function_name = "CreateFileW"
 
432
        LPTSTR = LPCWSTR
 
433
    else:
 
434
        _function_name = "CreateFileA"
 
435
        class LPTSTR(LPCSTR):
 
436
            def __new__(cls, obj):
 
437
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
 
438
 
 
439
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
440
    _CreateFile = ctypes.WINFUNCTYPE(
 
441
            HANDLE,                # return value
 
442
            LPTSTR,                # lpFileName
 
443
            DWORD,                 # dwDesiredAccess
 
444
            DWORD,                 # dwShareMode
 
445
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
446
            DWORD,                 # dwCreationDisposition
 
447
            DWORD,                 # dwFlagsAndAttributes
 
448
            HANDLE                 # hTemplateFile
 
449
        )((_function_name, ctypes.windll.kernel32))
 
450
 
 
451
    INVALID_HANDLE_VALUE = -1
 
452
 
 
453
    GENERIC_READ = 0x80000000
 
454
    GENERIC_WRITE = 0x40000000
 
455
    FILE_SHARE_READ = 1
 
456
    OPEN_ALWAYS = 4
 
457
    FILE_ATTRIBUTE_NORMAL = 128
 
458
 
 
459
    ERROR_ACCESS_DENIED = 5
 
460
    ERROR_SHARING_VIOLATION = 32
422
461
 
423
462
    class _ctypes_FileLock(_OSLock):
424
463
 
425
 
        def _lock(self, filename, openmode, lockmode):
426
 
            self._open(filename, openmode)
427
 
 
428
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
429
 
            overlapped = OVERLAPPED()
430
 
            result = _LockFileEx(self.hfile, # HANDLE hFile
431
 
                                 lockmode,   # DWORD dwFlags
432
 
                                 0,          # DWORD dwReserved
433
 
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
434
 
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
435
 
                                 ctypes.byref(overlapped), # lpOverlapped
436
 
                                )
437
 
            if result == 0:
438
 
                self._clear_f()
439
 
                last_err = _GetLastError()
440
 
                if last_err in (ERROR_LOCK_VIOLATION,):
441
 
                    raise errors.LockContention(filename)
442
 
                raise errors.LockContention('Unknown locking error: %s'
443
 
                                            % (last_err,))
 
464
        def _open(self, filename, access, share, cflags, pymode):
 
465
            self.filename = osutils.realpath(filename)
 
466
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
 
467
                FILE_ATTRIBUTE_NORMAL, 0)
 
468
            if handle in (INVALID_HANDLE_VALUE, 0):
 
469
                e = ctypes.WinError()
 
470
                if e.args[0] == ERROR_ACCESS_DENIED:
 
471
                    raise errors.LockFailed(filename, e)
 
472
                if e.args[0] == ERROR_SHARING_VIOLATION:
 
473
                    raise errors.LockContention(filename, e)
 
474
                raise e
 
475
            fd = msvcrt.open_osfhandle(handle, cflags)
 
476
            self.f = os.fdopen(fd, pymode)
 
477
            return self.f
444
478
 
445
479
        def unlock(self):
446
 
            overlapped = OVERLAPPED()
447
 
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
448
 
                                   0,          # DWORD dwReserved
449
 
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
450
 
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
451
 
                                   ctypes.byref(overlapped), # lpOverlapped
452
 
                                  )
453
480
            self._clear_f()
454
 
            if result == 0:
455
 
                self._clear_f()
456
 
                last_err = _GetLastError()
457
 
                raise errors.LockContention('Unknown unlocking error: %s'
458
 
                                            % (last_err,))
459
481
 
460
482
 
461
483
    class _ctypes_ReadLock(_ctypes_FileLock):
462
484
        def __init__(self, filename):
463
485
            super(_ctypes_ReadLock, self).__init__()
464
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
486
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
487
                "rb")
465
488
 
466
489
        def temporary_write_lock(self):
467
490
            """Try to grab a write lock on the file.
485
508
    class _ctypes_WriteLock(_ctypes_FileLock):
486
509
        def __init__(self, filename):
487
510
            super(_ctypes_WriteLock, self).__init__()
488
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
511
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
512
                "rb+")
489
513
 
490
514
        def restore_read_lock(self):
491
515
            """Restore the original ReadLock."""
508
532
# We default to using the first available lock class.
509
533
_lock_type, WriteLock, ReadLock = _lock_classes[0]
510
534
 
 
535
 
 
536
class _RelockDebugMixin(object):
 
537
    """Mixin support for -Drelock flag.
 
538
 
 
539
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
540
    acquiring a read- or write-lock.  If this object was previously locked (and
 
541
    locked the same way), and -Drelock is set, then this will trace.note a
 
542
    message about it.
 
543
    """
 
544
    
 
545
    _prev_lock = None
 
546
 
 
547
    def _note_lock(self, lock_type):
 
548
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
549
            if lock_type == 'r':
 
550
                type_name = 'read'
 
551
            else:
 
552
                type_name = 'write'
 
553
            trace.note('%r was %s locked again', self, type_name)
 
554
        self._prev_lock = lock_type
 
555