~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

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
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
 
 
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
50
from bzrlib.hooks import Hooks
46
 
 
 
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
 
 
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'] = []
 
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))
60
66
 
61
67
 
62
68
class Lock(object):
79
85
    def __eq__(self, other):
80
86
        return self.lock_url == other.lock_url and self.details == other.details
81
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
 
82
123
 
83
124
try:
84
125
    import fcntl
91
132
if sys.platform == 'win32':
92
133
    import msvcrt
93
134
    try:
94
 
        import win32con, win32file, pywintypes, winerror
 
135
        import win32file, pywintypes, winerror
95
136
        have_pywin32 = True
96
137
    except ImportError:
97
138
        pass
132
173
            self.f.close()
133
174
            self.f = None
134
175
 
135
 
    def __del__(self):
136
 
        if self.f:
137
 
            from warnings import warn
138
 
            warn("lock on %r not released" % self.f)
139
 
            self.unlock()
140
 
 
141
176
    def unlock(self):
142
177
        raise NotImplementedError()
143
178
 
146
181
 
147
182
 
148
183
if have_fcntl:
149
 
    LOCK_SH = fcntl.LOCK_SH
150
 
    LOCK_NB = fcntl.LOCK_NB
151
 
    lock_EX = fcntl.LOCK_EX
152
 
 
153
184
 
154
185
    class _fcntl_FileLock(_OSLock):
155
186
 
169
200
            if self.filename in _fcntl_WriteLock._open_locks:
170
201
                self._clear_f()
171
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,))
172
210
 
173
211
            self._open(self.filename, 'rb+')
174
212
            # 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.
 
213
            # at this point unlock() will be called, because self.f is set.
176
214
            # TODO: make this fully threadsafe, if we decide we care.
177
215
            _fcntl_WriteLock._open_locks.add(self.filename)
178
216
            try:
185
223
                    self.unlock()
186
224
                # we should be more precise about whats a locking
187
225
                # error and whats a random-other error
188
 
                raise errors.LockContention(e)
 
226
                raise errors.LockContention(self.filename, e)
189
227
 
190
228
        def unlock(self):
191
229
            _fcntl_WriteLock._open_locks.remove(self.filename)
199
237
        def __init__(self, filename):
200
238
            super(_fcntl_ReadLock, self).__init__()
201
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,))
202
248
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
203
249
            _fcntl_ReadLock._open_locks[self.filename] += 1
204
250
            self._open(filename, 'rb')
209
255
            except IOError, e:
210
256
                # we should be more precise about whats a locking
211
257
                # error and whats a random-other error
212
 
                raise errors.LockContention(e)
 
258
                raise errors.LockContention(self.filename, e)
213
259
 
214
260
        def unlock(self):
215
261
            count = _fcntl_ReadLock._open_locks[self.filename]
277
323
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
278
324
            except IOError, e:
279
325
                # TODO: Raise a more specific error based on the type of error
280
 
                raise errors.LockContention(e)
 
326
                raise errors.LockContention(self.filename, e)
281
327
            _fcntl_WriteLock._open_locks.add(self.filename)
282
328
 
283
329
            self.f = new_f
299
345
 
300
346
 
301
347
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
 
 
 
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
306
354
 
307
355
    class _w32c_FileLock(_OSLock):
308
356
 
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()
 
357
        def _open(self, filename, access, share, cflags, pymode):
 
358
            self.filename = osutils.realpath(filename)
314
359
            try:
315
 
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
316
 
                                     overlapped)
 
360
                self._handle = win32file_CreateFile(filename, access, share,
 
361
                    None, win32file.OPEN_ALWAYS,
 
362
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
317
363
            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()
 
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)
322
368
                raise
323
 
            except Exception, e:
324
 
                self._clear_f()
325
 
                raise errors.LockContention(e)
 
369
            fd = win32file._open_osfhandle(self._handle, cflags)
 
370
            self.f = os.fdopen(fd, pymode)
 
371
            return self.f
326
372
 
327
373
        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)
 
374
            self._clear_f()
 
375
            self._handle = None
334
376
 
335
377
 
336
378
    class _w32c_ReadLock(_w32c_FileLock):
337
379
        def __init__(self, filename):
338
380
            super(_w32c_ReadLock, self).__init__()
339
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
381
            self._open(filename, win32file.GENERIC_READ,
 
382
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
340
383
 
341
384
        def temporary_write_lock(self):
342
385
            """Try to grab a write lock on the file.
361
404
    class _w32c_WriteLock(_w32c_FileLock):
362
405
        def __init__(self, filename):
363
406
            super(_w32c_WriteLock, self).__init__()
364
 
            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+")
365
410
 
366
411
        def restore_read_lock(self):
367
412
            """Restore the original ReadLock."""
375
420
 
376
421
 
377
422
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
 
                   ]
 
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
422
457
 
423
458
    class _ctypes_FileLock(_OSLock):
424
459
 
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,))
 
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
444
474
 
445
475
        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
476
            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
477
 
460
478
 
461
479
    class _ctypes_ReadLock(_ctypes_FileLock):
462
480
        def __init__(self, filename):
463
481
            super(_ctypes_ReadLock, self).__init__()
464
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
482
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
483
                "rb")
465
484
 
466
485
        def temporary_write_lock(self):
467
486
            """Try to grab a write lock on the file.
485
504
    class _ctypes_WriteLock(_ctypes_FileLock):
486
505
        def __init__(self, filename):
487
506
            super(_ctypes_WriteLock, self).__init__()
488
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
507
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
508
                "rb+")
489
509
 
490
510
        def restore_read_lock(self):
491
511
            """Restore the original ReadLock."""
508
528
# We default to using the first available lock class.
509
529
_lock_type, WriteLock, ReadLock = _lock_classes[0]
510
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()