~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
16
17
 
17
18
"""Locking using OS file locks or file existence.
18
19
 
33
34
unlock() method.
34
35
"""
35
36
 
36
 
from __future__ import absolute_import
37
 
 
38
 
import contextlib
39
37
import errno
40
 
import os
41
38
import sys
42
 
import warnings
43
39
 
44
40
from bzrlib import (
45
 
    debug,
46
41
    errors,
47
42
    osutils,
48
43
    trace,
49
44
    )
50
 
from bzrlib.hooks import Hooks
51
 
from bzrlib.i18n import gettext
52
 
 
53
 
class LockHooks(Hooks):
54
 
 
55
 
    def __init__(self):
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))
66
 
 
67
 
 
68
 
class Lock(object):
69
 
    """Base class for locks.
70
 
 
71
 
    :cvar hooks: Hook dictionary for operations on locks.
72
 
    """
73
 
 
74
 
    hooks = LockHooks()
75
 
 
76
 
 
77
 
class LockResult(object):
78
 
    """Result of an operation on a lock; passed to a hook"""
79
 
 
80
 
    def __init__(self, lock_url, details=None):
81
 
        """Create a lock result for lock with optional details about the lock."""
82
 
        self.lock_url = lock_url
83
 
        self.details = details
84
 
 
85
 
    def __eq__(self, other):
86
 
        return self.lock_url == other.lock_url and self.details == other.details
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
 
 
123
 
 
124
 
try:
125
 
    import fcntl
126
 
    have_fcntl = True
127
 
except ImportError:
128
 
    have_fcntl = False
129
 
 
130
 
have_pywin32 = False
131
 
have_ctypes_win32 = False
132
 
if sys.platform == 'win32':
133
 
    import msvcrt
134
 
    try:
135
 
        import win32file, pywintypes, winerror
136
 
        have_pywin32 = True
137
 
    except ImportError:
138
 
        pass
139
 
 
140
 
    try:
141
 
        import ctypes
142
 
        have_ctypes_win32 = True
143
 
    except ImportError:
144
 
        pass
145
45
 
146
46
 
147
47
class _OSLock(object):
173
73
            self.f.close()
174
74
            self.f = None
175
75
 
 
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
 
176
82
    def unlock(self):
177
83
        raise NotImplementedError()
178
84
 
179
85
 
 
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
 
180
103
_lock_classes = []
181
104
 
182
105
 
183
106
if have_fcntl:
 
107
    LOCK_SH = fcntl.LOCK_SH
 
108
    LOCK_NB = fcntl.LOCK_NB
 
109
    lock_EX = fcntl.LOCK_EX
 
110
 
184
111
 
185
112
    class _fcntl_FileLock(_OSLock):
186
113
 
200
127
            if self.filename in _fcntl_WriteLock._open_locks:
201
128
                self._clear_f()
202
129
                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,))
210
130
 
211
131
            self._open(self.filename, 'rb+')
212
132
            # reserve a slot for this lock - even if the lockf call fails,
213
 
            # at this point unlock() will be called, because self.f is set.
 
133
            # at thisi point unlock() will be called, because self.f is set.
214
134
            # TODO: make this fully threadsafe, if we decide we care.
215
135
            _fcntl_WriteLock._open_locks.add(self.filename)
216
136
            try:
223
143
                    self.unlock()
224
144
                # we should be more precise about whats a locking
225
145
                # error and whats a random-other error
226
 
                raise errors.LockContention(self.filename, e)
 
146
                raise errors.LockContention(e)
227
147
 
228
148
        def unlock(self):
229
149
            _fcntl_WriteLock._open_locks.remove(self.filename)
237
157
        def __init__(self, filename):
238
158
            super(_fcntl_ReadLock, self).__init__()
239
159
            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,))
248
160
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
249
161
            _fcntl_ReadLock._open_locks[self.filename] += 1
250
162
            self._open(filename, 'rb')
255
167
            except IOError, e:
256
168
                # we should be more precise about whats a locking
257
169
                # error and whats a random-other error
258
 
                raise errors.LockContention(self.filename, e)
 
170
                raise errors.LockContention(e)
259
171
 
260
172
        def unlock(self):
261
173
            count = _fcntl_ReadLock._open_locks[self.filename]
323
235
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
324
236
            except IOError, e:
325
237
                # TODO: Raise a more specific error based on the type of error
326
 
                raise errors.LockContention(self.filename, e)
 
238
                raise errors.LockContention(e)
327
239
            _fcntl_WriteLock._open_locks.add(self.filename)
328
240
 
329
241
            self.f = new_f
345
257
 
346
258
 
347
259
if have_pywin32 and sys.platform == 'win32':
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
 
260
    LOCK_SH = 0 # the default
 
261
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
262
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
263
 
354
264
 
355
265
    class _w32c_FileLock(_OSLock):
356
266
 
357
 
        def _open(self, filename, access, share, cflags, pymode):
358
 
            self.filename = osutils.realpath(filename)
 
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()
359
272
            try:
360
 
                self._handle = win32file_CreateFile(filename, access, share,
361
 
                    None, win32file.OPEN_ALWAYS,
362
 
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
273
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
274
                                     overlapped)
363
275
            except pywintypes.error, e:
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)
 
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()
368
280
                raise
369
 
            fd = win32file._open_osfhandle(self._handle, cflags)
370
 
            self.f = os.fdopen(fd, pymode)
371
 
            return self.f
 
281
            except Exception, e:
 
282
                self._clear_f()
 
283
                raise errors.LockContention(e)
372
284
 
373
285
        def unlock(self):
374
 
            self._clear_f()
375
 
            self._handle = None
 
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)
376
292
 
377
293
 
378
294
    class _w32c_ReadLock(_w32c_FileLock):
379
295
        def __init__(self, filename):
380
296
            super(_w32c_ReadLock, self).__init__()
381
 
            self._open(filename, win32file.GENERIC_READ,
382
 
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
297
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
383
298
 
384
299
        def temporary_write_lock(self):
385
300
            """Try to grab a write lock on the file.
404
319
    class _w32c_WriteLock(_w32c_FileLock):
405
320
        def __init__(self, filename):
406
321
            super(_w32c_WriteLock, self).__init__()
407
 
            self._open(filename,
408
 
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
409
 
                os.O_RDWR, "rb+")
 
322
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
410
323
 
411
324
        def restore_read_lock(self):
412
325
            """Restore the original ReadLock."""
419
332
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
420
333
 
421
334
 
422
 
if have_ctypes_win32:
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
 
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
                   ]
457
380
 
458
381
    class _ctypes_FileLock(_OSLock):
459
382
 
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
 
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,))
474
402
 
475
403
        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
                                  )
476
411
            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,))
477
417
 
478
418
 
479
419
    class _ctypes_ReadLock(_ctypes_FileLock):
480
420
        def __init__(self, filename):
481
421
            super(_ctypes_ReadLock, self).__init__()
482
 
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
483
 
                "rb")
 
422
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
484
423
 
485
424
        def temporary_write_lock(self):
486
425
            """Try to grab a write lock on the file.
504
443
    class _ctypes_WriteLock(_ctypes_FileLock):
505
444
        def __init__(self, filename):
506
445
            super(_ctypes_WriteLock, self).__init__()
507
 
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
508
 
                "rb+")
 
446
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
509
447
 
510
448
        def restore_read_lock(self):
511
449
            """Restore the original ReadLock."""
528
466
# We default to using the first available lock class.
529
467
_lock_type, WriteLock, ReadLock = _lock_classes[0]
530
468
 
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()