~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Patch Queue Manager
  • Date: 2014-02-12 18:22:22 UTC
  • mfrom: (6589.2.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20140212182222-beouo25gaf1cny76
(vila) The XDG Base Directory Specification uses the XDG_CACHE_HOME,
 not XDG_CACHE_DIR. (Andrew Starr-Bochicchio)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
 
18
17
"""Locking using OS file locks or file existence.
19
18
 
20
19
Note: This method of locking is generally deprecated in favour of LockDir, but
34
33
unlock() method.
35
34
"""
36
35
 
 
36
from __future__ import absolute_import
 
37
 
 
38
import contextlib
37
39
import errno
 
40
import os
38
41
import sys
 
42
import warnings
39
43
 
40
44
from bzrlib import (
 
45
    debug,
41
46
    errors,
42
47
    osutils,
43
48
    trace,
44
49
    )
45
 
from bzrlib.hooks import HookPoint, Hooks
46
 
 
 
50
from bzrlib.hooks import Hooks
 
51
from bzrlib.i18n import gettext
47
52
 
48
53
class LockHooks(Hooks):
49
54
 
50
55
    def __init__(self):
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))
 
56
        Hooks.__init__(self, "bzrlib.lock", "Lock.hooks")
 
57
        self.add_hook('lock_acquired',
 
58
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
59
            "acquired.", (1, 8))
 
60
        self.add_hook('lock_released',
 
61
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
62
            "released.", (1, 8))
 
63
        self.add_hook('lock_broken',
 
64
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
65
            "broken.", (1, 15))
58
66
 
59
67
 
60
68
class Lock(object):
77
85
    def __eq__(self, other):
78
86
        return self.lock_url == other.lock_url and self.details == other.details
79
87
 
 
88
    def __repr__(self):
 
89
        return '%s(%s, %s)' % (self.__class__.__name__,
 
90
                             self.lock_url, self.details)
 
91
 
 
92
 
 
93
class LogicalLockResult(object):
 
94
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
 
95
 
 
96
    :ivar unlock: A callable which will unlock the lock.
 
97
    """
 
98
 
 
99
    def __init__(self, unlock):
 
100
        self.unlock = unlock
 
101
 
 
102
    def __repr__(self):
 
103
        return "LogicalLockResult(%s)" % (self.unlock)
 
104
 
 
105
 
 
106
 
 
107
def cant_unlock_not_held(locked_object):
 
108
    """An attempt to unlock failed because the object was not locked.
 
109
 
 
110
    This provides a policy point from which we can generate either a warning 
 
111
    or an exception.
 
112
    """
 
113
    # This is typically masking some other error and called from a finally
 
114
    # block, so it's useful to have the option not to generate a new error
 
115
    # here.  You can use -Werror to make it fatal.  It should possibly also
 
116
    # raise LockNotHeld.
 
117
    if 'unlock' in debug.debug_flags:
 
118
        warnings.warn("%r is already unlocked" % (locked_object,),
 
119
            stacklevel=3)
 
120
    else:
 
121
        raise errors.LockNotHeld(locked_object)
 
122
 
80
123
 
81
124
try:
82
125
    import fcntl
89
132
if sys.platform == 'win32':
90
133
    import msvcrt
91
134
    try:
92
 
        import win32con, win32file, pywintypes, winerror
 
135
        import win32file, pywintypes, winerror
93
136
        have_pywin32 = True
94
137
    except ImportError:
95
138
        pass
130
173
            self.f.close()
131
174
            self.f = None
132
175
 
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
 
 
139
176
    def unlock(self):
140
177
        raise NotImplementedError()
141
178
 
144
181
 
145
182
 
146
183
if have_fcntl:
147
 
    LOCK_SH = fcntl.LOCK_SH
148
 
    LOCK_NB = fcntl.LOCK_NB
149
 
    lock_EX = fcntl.LOCK_EX
150
 
 
151
184
 
152
185
    class _fcntl_FileLock(_OSLock):
153
186
 
167
200
            if self.filename in _fcntl_WriteLock._open_locks:
168
201
                self._clear_f()
169
202
                raise errors.LockContention(self.filename)
 
203
            if self.filename in _fcntl_ReadLock._open_locks:
 
204
                if 'strict_locks' in debug.debug_flags:
 
205
                    self._clear_f()
 
206
                    raise errors.LockContention(self.filename)
 
207
                else:
 
208
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
209
                                 % (self.filename,))
170
210
 
171
211
            self._open(self.filename, 'rb+')
172
212
            # reserve a slot for this lock - even if the lockf call fails,
173
 
            # at thisi point unlock() will be called, because self.f is set.
 
213
            # at this point unlock() will be called, because self.f is set.
174
214
            # TODO: make this fully threadsafe, if we decide we care.
175
215
            _fcntl_WriteLock._open_locks.add(self.filename)
176
216
            try:
197
237
        def __init__(self, filename):
198
238
            super(_fcntl_ReadLock, self).__init__()
199
239
            self.filename = osutils.realpath(filename)
 
240
            if self.filename in _fcntl_WriteLock._open_locks:
 
241
                if 'strict_locks' in debug.debug_flags:
 
242
                    # We raise before calling _open so we don't need to
 
243
                    # _clear_f
 
244
                    raise errors.LockContention(self.filename)
 
245
                else:
 
246
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
247
                                 % (self.filename,))
200
248
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
201
249
            _fcntl_ReadLock._open_locks[self.filename] += 1
202
250
            self._open(filename, 'rb')
297
345
 
298
346
 
299
347
if have_pywin32 and sys.platform == 'win32':
300
 
    LOCK_SH = 0 # the default
301
 
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
302
 
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
303
 
 
 
348
    if os.path.supports_unicode_filenames:
 
349
        # for Windows NT/2K/XP/etc
 
350
        win32file_CreateFile = win32file.CreateFileW
 
351
    else:
 
352
        # for Windows 98
 
353
        win32file_CreateFile = win32file.CreateFile
304
354
 
305
355
    class _w32c_FileLock(_OSLock):
306
356
 
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
        def _open(self, filename, access, share, cflags, pymode):
 
358
            self.filename = osutils.realpath(filename)
312
359
            try:
313
 
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
314
 
                                     overlapped)
 
360
                self._handle = win32file_CreateFile(filename, access, share,
 
361
                    None, win32file.OPEN_ALWAYS,
 
362
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
315
363
            except pywintypes.error, 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()
 
364
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
365
                    raise errors.LockFailed(filename, e)
 
366
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
367
                    raise errors.LockContention(filename, e)
320
368
                raise
321
 
            except Exception, e:
322
 
                self._clear_f()
323
 
                raise errors.LockContention(filename, e)
 
369
            fd = win32file._open_osfhandle(self._handle, cflags)
 
370
            self.f = os.fdopen(fd, pymode)
 
371
            return self.f
324
372
 
325
373
        def unlock(self):
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
            self._clear_f()
 
375
            self._handle = None
332
376
 
333
377
 
334
378
    class _w32c_ReadLock(_w32c_FileLock):
335
379
        def __init__(self, filename):
336
380
            super(_w32c_ReadLock, self).__init__()
337
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
381
            self._open(filename, win32file.GENERIC_READ,
 
382
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
338
383
 
339
384
        def temporary_write_lock(self):
340
385
            """Try to grab a write lock on the file.
359
404
    class _w32c_WriteLock(_w32c_FileLock):
360
405
        def __init__(self, filename):
361
406
            super(_w32c_WriteLock, self).__init__()
362
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
407
            self._open(filename,
 
408
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
409
                os.O_RDWR, "rb+")
363
410
 
364
411
        def restore_read_lock(self):
365
412
            """Restore the original ReadLock."""
373
420
 
374
421
 
375
422
if have_ctypes_win32:
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
 
                   ]
 
423
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
424
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
425
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
426
    if os.path.supports_unicode_filenames:
 
427
        _function_name = "CreateFileW"
 
428
        LPTSTR = LPCWSTR
 
429
    else:
 
430
        _function_name = "CreateFileA"
 
431
        class LPTSTR(LPCSTR):
 
432
            def __new__(cls, obj):
 
433
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
 
434
 
 
435
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
436
    _CreateFile = ctypes.WINFUNCTYPE(
 
437
            HANDLE,                # return value
 
438
            LPTSTR,                # lpFileName
 
439
            DWORD,                 # dwDesiredAccess
 
440
            DWORD,                 # dwShareMode
 
441
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
442
            DWORD,                 # dwCreationDisposition
 
443
            DWORD,                 # dwFlagsAndAttributes
 
444
            HANDLE                 # hTemplateFile
 
445
        )((_function_name, ctypes.windll.kernel32))
 
446
 
 
447
    INVALID_HANDLE_VALUE = -1
 
448
 
 
449
    GENERIC_READ = 0x80000000
 
450
    GENERIC_WRITE = 0x40000000
 
451
    FILE_SHARE_READ = 1
 
452
    OPEN_ALWAYS = 4
 
453
    FILE_ATTRIBUTE_NORMAL = 128
 
454
 
 
455
    ERROR_ACCESS_DENIED = 5
 
456
    ERROR_SHARING_VIOLATION = 32
420
457
 
421
458
    class _ctypes_FileLock(_OSLock):
422
459
 
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,))
 
460
        def _open(self, filename, access, share, cflags, pymode):
 
461
            self.filename = osutils.realpath(filename)
 
462
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
 
463
                FILE_ATTRIBUTE_NORMAL, 0)
 
464
            if handle in (INVALID_HANDLE_VALUE, 0):
 
465
                e = ctypes.WinError()
 
466
                if e.args[0] == ERROR_ACCESS_DENIED:
 
467
                    raise errors.LockFailed(filename, e)
 
468
                if e.args[0] == ERROR_SHARING_VIOLATION:
 
469
                    raise errors.LockContention(filename, e)
 
470
                raise e
 
471
            fd = msvcrt.open_osfhandle(handle, cflags)
 
472
            self.f = os.fdopen(fd, pymode)
 
473
            return self.f
442
474
 
443
475
        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
 
                                  )
451
476
            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,))
457
477
 
458
478
 
459
479
    class _ctypes_ReadLock(_ctypes_FileLock):
460
480
        def __init__(self, filename):
461
481
            super(_ctypes_ReadLock, self).__init__()
462
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
482
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
483
                "rb")
463
484
 
464
485
        def temporary_write_lock(self):
465
486
            """Try to grab a write lock on the file.
483
504
    class _ctypes_WriteLock(_ctypes_FileLock):
484
505
        def __init__(self, filename):
485
506
            super(_ctypes_WriteLock, self).__init__()
486
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
507
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
508
                "rb+")
487
509
 
488
510
        def restore_read_lock(self):
489
511
            """Restore the original ReadLock."""
506
528
# We default to using the first available lock class.
507
529
_lock_type, WriteLock, ReadLock = _lock_classes[0]
508
530
 
 
531
 
 
532
class _RelockDebugMixin(object):
 
533
    """Mixin support for -Drelock flag.
 
534
 
 
535
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
536
    acquiring a read- or write-lock.  If this object was previously locked (and
 
537
    locked the same way), and -Drelock is set, then this will trace.note a
 
538
    message about it.
 
539
    """
 
540
 
 
541
    _prev_lock = None
 
542
 
 
543
    def _note_lock(self, lock_type):
 
544
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
545
            if lock_type == 'r':
 
546
                type_name = 'read'
 
547
            else:
 
548
                type_name = 'write'
 
549
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
550
        self._prev_lock = lock_type
 
551
 
 
552
@contextlib.contextmanager
 
553
def write_locked(lockable):
 
554
    lockable.lock_write()
 
555
    try:
 
556
        yield lockable
 
557
    finally:
 
558
        lockable.unlock()