~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Andrew Bennetts
  • Date: 2009-04-02 05:53:12 UTC
  • mto: This revision was merged to the branch mainline in revision 4242.
  • Revision ID: andrew.bennetts@canonical.com-20090402055312-h7mvgumvm7e620mj
Fix nits in spelling and naming.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 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
35
35
"""
36
36
 
37
37
import errno
38
 
import os
39
38
import sys
40
 
import warnings
41
39
 
42
40
from bzrlib import (
43
 
    debug,
44
41
    errors,
45
42
    osutils,
46
43
    trace,
47
44
    )
48
 
from bzrlib.hooks import Hooks
 
45
from bzrlib.hooks import HookPoint, Hooks
49
46
 
50
47
 
51
48
class LockHooks(Hooks):
52
49
 
53
50
    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))
 
51
        Hooks.__init__(self)
 
52
        self.create_hook(HookPoint('lock_acquired',
 
53
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
54
            "acquired.", (1, 8), None))
 
55
        self.create_hook(HookPoint('lock_released',
 
56
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
57
            "released.", (1, 8), None))
64
58
 
65
59
 
66
60
class Lock(object):
83
77
    def __eq__(self, other):
84
78
        return self.lock_url == other.lock_url and self.details == other.details
85
79
 
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
80
 
122
81
try:
123
82
    import fcntl
130
89
if sys.platform == 'win32':
131
90
    import msvcrt
132
91
    try:
133
 
        import win32file, pywintypes, winerror
 
92
        import win32con, win32file, pywintypes, winerror
134
93
        have_pywin32 = True
135
94
    except ImportError:
136
95
        pass
171
130
            self.f.close()
172
131
            self.f = None
173
132
 
 
133
    def __del__(self):
 
134
        if self.f:
 
135
            from warnings import warn
 
136
            warn("lock on %r not released" % self.f)
 
137
            self.unlock()
 
138
 
174
139
    def unlock(self):
175
140
        raise NotImplementedError()
176
141
 
179
144
 
180
145
 
181
146
if have_fcntl:
 
147
    LOCK_SH = fcntl.LOCK_SH
 
148
    LOCK_NB = fcntl.LOCK_NB
 
149
    lock_EX = fcntl.LOCK_EX
 
150
 
182
151
 
183
152
    class _fcntl_FileLock(_OSLock):
184
153
 
198
167
            if self.filename in _fcntl_WriteLock._open_locks:
199
168
                self._clear_f()
200
169
                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,))
208
170
 
209
171
            self._open(self.filename, 'rb+')
210
172
            # reserve a slot for this lock - even if the lockf call fails,
211
 
            # at this point unlock() will be called, because self.f is set.
 
173
            # at thisi point unlock() will be called, because self.f is set.
212
174
            # TODO: make this fully threadsafe, if we decide we care.
213
175
            _fcntl_WriteLock._open_locks.add(self.filename)
214
176
            try:
235
197
        def __init__(self, filename):
236
198
            super(_fcntl_ReadLock, self).__init__()
237
199
            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,))
246
200
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
247
201
            _fcntl_ReadLock._open_locks[self.filename] += 1
248
202
            self._open(filename, 'rb')
343
297
 
344
298
 
345
299
if have_pywin32 and sys.platform == 'win32':
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
 
300
    LOCK_SH = 0 # the default
 
301
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
302
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
303
 
352
304
 
353
305
    class _w32c_FileLock(_OSLock):
354
306
 
355
 
        def _open(self, filename, access, share, cflags, pymode):
356
 
            self.filename = osutils.realpath(filename)
 
307
        def _lock(self, filename, openmode, lockmode):
 
308
            self._open(filename, openmode)
 
309
 
 
310
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
311
            overlapped = pywintypes.OVERLAPPED()
357
312
            try:
358
 
                self._handle = win32file_CreateFile(filename, access, share,
359
 
                    None, win32file.OPEN_ALWAYS,
360
 
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
313
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
314
                                     overlapped)
361
315
            except pywintypes.error, e:
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)
 
316
                self._clear_f()
 
317
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
 
318
                    raise errors.LockContention(filename)
 
319
                ## import pdb; pdb.set_trace()
366
320
                raise
367
 
            fd = win32file._open_osfhandle(self._handle, cflags)
368
 
            self.f = os.fdopen(fd, pymode)
369
 
            return self.f
 
321
            except Exception, e:
 
322
                self._clear_f()
 
323
                raise errors.LockContention(filename, e)
370
324
 
371
325
        def unlock(self):
372
 
            self._clear_f()
373
 
            self._handle = None
 
326
            overlapped = pywintypes.OVERLAPPED()
 
327
            try:
 
328
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
329
                self._clear_f()
 
330
            except Exception, e:
 
331
                raise errors.LockContention(self.filename, e)
374
332
 
375
333
 
376
334
    class _w32c_ReadLock(_w32c_FileLock):
377
335
        def __init__(self, filename):
378
336
            super(_w32c_ReadLock, self).__init__()
379
 
            self._open(filename, win32file.GENERIC_READ,
380
 
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
337
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
381
338
 
382
339
        def temporary_write_lock(self):
383
340
            """Try to grab a write lock on the file.
402
359
    class _w32c_WriteLock(_w32c_FileLock):
403
360
        def __init__(self, filename):
404
361
            super(_w32c_WriteLock, self).__init__()
405
 
            self._open(filename,
406
 
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
407
 
                os.O_RDWR, "rb+")
 
362
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
408
363
 
409
364
        def restore_read_lock(self):
410
365
            """Restore the original ReadLock."""
418
373
 
419
374
 
420
375
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
    # These constants were copied from the win32con.py module.
 
377
    LOCKFILE_FAIL_IMMEDIATELY = 1
 
378
    LOCKFILE_EXCLUSIVE_LOCK = 2
 
379
    # Constant taken from winerror.py module
 
380
    ERROR_LOCK_VIOLATION = 33
 
381
 
 
382
    LOCK_SH = 0
 
383
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
 
384
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
 
385
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
 
386
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
 
387
    _GetLastError = ctypes.windll.kernel32.GetLastError
 
388
 
 
389
    ### Define the OVERLAPPED structure.
 
390
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
 
391
    # typedef struct _OVERLAPPED {
 
392
    #   ULONG_PTR Internal;
 
393
    #   ULONG_PTR InternalHigh;
 
394
    #   union {
 
395
    #     struct {
 
396
    #       DWORD Offset;
 
397
    #       DWORD OffsetHigh;
 
398
    #     };
 
399
    #     PVOID Pointer;
 
400
    #   };
 
401
    #   HANDLE hEvent;
 
402
    # } OVERLAPPED,
 
403
 
 
404
    class _inner_struct(ctypes.Structure):
 
405
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
 
406
                    ('OffsetHigh', ctypes.c_uint), # DWORD
 
407
                   ]
 
408
 
 
409
    class _inner_union(ctypes.Union):
 
410
        _fields_  = [('anon_struct', _inner_struct), # struct
 
411
                     ('Pointer', ctypes.c_void_p), # PVOID
 
412
                    ]
 
413
 
 
414
    class OVERLAPPED(ctypes.Structure):
 
415
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
 
416
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
 
417
                    ('_inner_union', _inner_union),
 
418
                    ('hEvent', ctypes.c_void_p), # HANDLE
 
419
                   ]
455
420
 
456
421
    class _ctypes_FileLock(_OSLock):
457
422
 
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
 
423
        def _lock(self, filename, openmode, lockmode):
 
424
            self._open(filename, openmode)
 
425
 
 
426
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
427
            overlapped = OVERLAPPED()
 
428
            result = _LockFileEx(self.hfile, # HANDLE hFile
 
429
                                 lockmode,   # DWORD dwFlags
 
430
                                 0,          # DWORD dwReserved
 
431
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
432
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
 
433
                                 ctypes.byref(overlapped), # lpOverlapped
 
434
                                )
 
435
            if result == 0:
 
436
                self._clear_f()
 
437
                last_err = _GetLastError()
 
438
                if last_err in (ERROR_LOCK_VIOLATION,):
 
439
                    raise errors.LockContention(filename)
 
440
                raise errors.LockContention(filename,
 
441
                    'Unknown locking error: %s' % (last_err,))
472
442
 
473
443
        def unlock(self):
 
444
            overlapped = OVERLAPPED()
 
445
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
 
446
                                   0,          # DWORD dwReserved
 
447
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
448
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
 
449
                                   ctypes.byref(overlapped), # lpOverlapped
 
450
                                  )
474
451
            self._clear_f()
 
452
            if result == 0:
 
453
                self._clear_f()
 
454
                last_err = _GetLastError()
 
455
                raise errors.LockContention(self.filename,
 
456
                    'Unknown unlocking error: %s' % (last_err,))
475
457
 
476
458
 
477
459
    class _ctypes_ReadLock(_ctypes_FileLock):
478
460
        def __init__(self, filename):
479
461
            super(_ctypes_ReadLock, self).__init__()
480
 
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
481
 
                "rb")
 
462
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
482
463
 
483
464
        def temporary_write_lock(self):
484
465
            """Try to grab a write lock on the file.
502
483
    class _ctypes_WriteLock(_ctypes_FileLock):
503
484
        def __init__(self, filename):
504
485
            super(_ctypes_WriteLock, self).__init__()
505
 
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
506
 
                "rb+")
 
486
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
507
487
 
508
488
        def restore_read_lock(self):
509
489
            """Restore the original ReadLock."""
526
506
# We default to using the first available lock class.
527
507
_lock_type, WriteLock, ReadLock = _lock_classes[0]
528
508
 
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('%r was %s locked again', self, type_name)
548
 
        self._prev_lock = lock_type
549