/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
17
18
"""Locking using OS file locks or file existence.
18
19
 
19
20
Note: This method of locking is generally deprecated in favour of LockDir, but
33
34
unlock() method.
34
35
"""
35
36
 
36
 
from __future__ import absolute_import
37
 
 
38
 
import contextlib
39
37
import errno
40
38
import os
41
39
import sys
42
40
import warnings
43
41
 
44
 
from . import (
 
42
from bzrlib import (
45
43
    debug,
46
44
    errors,
47
45
    osutils,
48
46
    trace,
49
47
    )
50
 
from .hooks import Hooks
51
 
from .i18n import gettext
 
48
from bzrlib.hooks import HookPoint, Hooks
52
49
 
53
50
 
54
51
class LockHooks(Hooks):
55
52
 
56
53
    def __init__(self):
57
 
        Hooks.__init__(self, "breezy.lock", "Lock.hooks")
58
 
        self.add_hook(
59
 
            'lock_acquired',
60
 
            "Called with a breezy.lock.LockResult when a physical lock is "
61
 
            "acquired.", (1, 8))
62
 
        self.add_hook(
63
 
            'lock_released',
64
 
            "Called with a breezy.lock.LockResult when a physical lock is "
65
 
            "released.", (1, 8))
66
 
        self.add_hook(
67
 
            'lock_broken',
68
 
            "Called with a breezy.lock.LockResult when a physical lock is "
69
 
            "broken.", (1, 15))
 
54
        Hooks.__init__(self)
 
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))
70
64
 
71
65
 
72
66
class Lock(object):
91
85
 
92
86
    def __repr__(self):
93
87
        return '%s(%s, %s)' % (self.__class__.__name__,
94
 
                               self.lock_url, self.details)
95
 
 
96
 
 
97
 
class LogicalLockResult(object):
98
 
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
99
 
 
100
 
    :ivar unlock: A callable which will unlock the lock.
101
 
    """
102
 
 
103
 
    def __init__(self, unlock, token=None):
104
 
        self.unlock = unlock
105
 
        self.token = token
106
 
 
107
 
    def __repr__(self):
108
 
        return "LogicalLockResult(%s)" % (self.unlock)
109
 
 
110
 
    def __enter__(self):
111
 
        return self
112
 
 
113
 
    def __exit__(self, exc_type, exc_val, exc_tb):
114
 
        # If there was an error raised, prefer the original one
115
 
        try:
116
 
            self.unlock()
117
 
        except BaseException:
118
 
            if exc_type is None:
119
 
                raise
120
 
        return False
 
88
                             self.lock_url, self.details)
121
89
 
122
90
 
123
91
def cant_unlock_not_held(locked_object):
124
92
    """An attempt to unlock failed because the object was not locked.
125
93
 
126
 
    This provides a policy point from which we can generate either a warning or
127
 
    an exception.
 
94
    This provides a policy point from which we can generate either a warning 
 
95
    or an exception.
128
96
    """
129
97
    # This is typically masking some other error and called from a finally
130
98
    # block, so it's useful to have the option not to generate a new error
132
100
    # raise LockNotHeld.
133
101
    if 'unlock' in debug.debug_flags:
134
102
        warnings.warn("%r is already unlocked" % (locked_object,),
135
 
                      stacklevel=3)
 
103
            stacklevel=3)
136
104
    else:
137
105
        raise errors.LockNotHeld(locked_object)
138
106
 
143
111
except ImportError:
144
112
    have_fcntl = False
145
113
 
 
114
have_pywin32 = False
146
115
have_ctypes_win32 = False
147
116
if sys.platform == 'win32':
148
117
    import msvcrt
149
118
    try:
 
119
        import win32file, pywintypes, winerror
 
120
        have_pywin32 = True
 
121
    except ImportError:
 
122
        pass
 
123
 
 
124
    try:
150
125
        import ctypes
151
126
        have_ctypes_win32 = True
152
127
    except ImportError:
164
139
        try:
165
140
            self.f = open(self.filename, filemode)
166
141
            return self.f
167
 
        except IOError as e:
 
142
        except IOError, e:
168
143
            if e.errno in (errno.EACCES, errno.EPERM):
169
144
                raise errors.LockFailed(self.filename, str(e))
170
145
            if e.errno != errno.ENOENT:
182
157
            self.f.close()
183
158
            self.f = None
184
159
 
 
160
    def __del__(self):
 
161
        if self.f:
 
162
            from warnings import warn
 
163
            warn("lock on %r not released" % self.f)
 
164
            self.unlock()
 
165
 
185
166
    def unlock(self):
186
167
        raise NotImplementedError()
187
168
 
197
178
            fcntl.lockf(self.f, fcntl.LOCK_UN)
198
179
            self._clear_f()
199
180
 
 
181
 
200
182
    class _fcntl_WriteLock(_fcntl_FileLock):
201
183
 
202
184
        _open_locks = set()
225
207
                # LOCK_NB will cause IOError to be raised if we can't grab a
226
208
                # lock right away.
227
209
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
228
 
            except IOError as e:
 
210
            except IOError, e:
229
211
                if e.errno in (errno.EAGAIN, errno.EACCES):
230
212
                    # We couldn't grab the lock
231
213
                    self.unlock()
237
219
            _fcntl_WriteLock._open_locks.remove(self.filename)
238
220
            self._unlock()
239
221
 
 
222
 
240
223
    class _fcntl_ReadLock(_fcntl_FileLock):
241
224
 
242
225
        _open_locks = {}
259
242
                # LOCK_NB will cause IOError to be raised if we can't grab a
260
243
                # lock right away.
261
244
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
262
 
            except IOError as e:
 
245
            except IOError, e:
263
246
                # we should be more precise about whats a locking
264
247
                # error and whats a random-other error
265
248
                raise errors.LockContention(self.filename, e)
284
267
            """
285
268
            if self.filename in _fcntl_WriteLock._open_locks:
286
269
                raise AssertionError('file already locked: %r'
287
 
                                     % (self.filename,))
 
270
                    % (self.filename,))
288
271
            try:
289
272
                wlock = _fcntl_TemporaryWriteLock(self)
290
273
            except errors.LockError:
292
275
                return False, self
293
276
            return True, wlock
294
277
 
 
278
 
295
279
    class _fcntl_TemporaryWriteLock(_OSLock):
296
280
        """A token used when grabbing a temporary_write_lock.
297
281
 
311
295
 
312
296
            if self.filename in _fcntl_WriteLock._open_locks:
313
297
                raise AssertionError('file already locked: %r'
314
 
                                     % (self.filename,))
 
298
                    % (self.filename,))
315
299
 
316
300
            # See if we can open the file for writing. Another process might
317
301
            # have a read lock. We don't use self._open() because we don't want
319
303
            # done by _fcntl_ReadLock
320
304
            try:
321
305
                new_f = open(self.filename, 'rb+')
322
 
            except IOError as e:
 
306
            except IOError, e:
323
307
                if e.errno in (errno.EACCES, errno.EPERM):
324
308
                    raise errors.LockFailed(self.filename, str(e))
325
309
                raise
327
311
                # LOCK_NB will cause IOError to be raised if we can't grab a
328
312
                # lock right away.
329
313
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
330
 
            except IOError as e:
 
314
            except IOError, e:
331
315
                # TODO: Raise a more specific error based on the type of error
332
316
                raise errors.LockContention(self.filename, e)
333
317
            _fcntl_WriteLock._open_locks.add(self.filename)
336
320
 
337
321
        def restore_read_lock(self):
338
322
            """Restore the original ReadLock."""
339
 
            # For fcntl, since we never released the read lock, just release
340
 
            # the write lock, and return the original lock.
 
323
            # For fcntl, since we never released the read lock, just release the
 
324
            # write lock, and return the original lock.
341
325
            fcntl.lockf(self.f, fcntl.LOCK_UN)
342
326
            self._clear_f()
343
327
            _fcntl_WriteLock._open_locks.remove(self.filename)
346
330
            self._read_lock = None
347
331
            return read_lock
348
332
 
 
333
 
349
334
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
350
335
 
351
336
 
 
337
if have_pywin32 and sys.platform == 'win32':
 
338
    if os.path.supports_unicode_filenames:
 
339
        # for Windows NT/2K/XP/etc
 
340
        win32file_CreateFile = win32file.CreateFileW
 
341
    else:
 
342
        # for Windows 98
 
343
        win32file_CreateFile = win32file.CreateFile
 
344
 
 
345
    class _w32c_FileLock(_OSLock):
 
346
 
 
347
        def _open(self, filename, access, share, cflags, pymode):
 
348
            self.filename = osutils.realpath(filename)
 
349
            try:
 
350
                self._handle = win32file_CreateFile(filename, access, share,
 
351
                    None, win32file.OPEN_ALWAYS,
 
352
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
353
            except pywintypes.error, e:
 
354
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
355
                    raise errors.LockFailed(filename, e)
 
356
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
357
                    raise errors.LockContention(filename, e)
 
358
                raise
 
359
            fd = win32file._open_osfhandle(self._handle, cflags)
 
360
            self.f = os.fdopen(fd, pymode)
 
361
            return self.f
 
362
 
 
363
        def unlock(self):
 
364
            self._clear_f()
 
365
            self._handle = None
 
366
 
 
367
 
 
368
    class _w32c_ReadLock(_w32c_FileLock):
 
369
        def __init__(self, filename):
 
370
            super(_w32c_ReadLock, self).__init__()
 
371
            self._open(filename, win32file.GENERIC_READ,
 
372
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
373
 
 
374
        def temporary_write_lock(self):
 
375
            """Try to grab a write lock on the file.
 
376
 
 
377
            On platforms that support it, this will upgrade to a write lock
 
378
            without unlocking the file.
 
379
            Otherwise, this will release the read lock, and try to acquire a
 
380
            write lock.
 
381
 
 
382
            :return: A token which can be used to switch back to a read lock.
 
383
            """
 
384
            # I can't find a way to upgrade a read lock to a write lock without
 
385
            # unlocking first. So here, we do just that.
 
386
            self.unlock()
 
387
            try:
 
388
                wlock = _w32c_WriteLock(self.filename)
 
389
            except errors.LockError:
 
390
                return False, _w32c_ReadLock(self.filename)
 
391
            return True, wlock
 
392
 
 
393
 
 
394
    class _w32c_WriteLock(_w32c_FileLock):
 
395
        def __init__(self, filename):
 
396
            super(_w32c_WriteLock, self).__init__()
 
397
            self._open(filename,
 
398
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
399
                os.O_RDWR, "rb+")
 
400
 
 
401
        def restore_read_lock(self):
 
402
            """Restore the original ReadLock."""
 
403
            # For win32 we had to completely let go of the original lock, so we
 
404
            # just unlock and create a new read lock.
 
405
            self.unlock()
 
406
            return _w32c_ReadLock(self.filename)
 
407
 
 
408
 
 
409
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
410
 
 
411
 
352
412
if have_ctypes_win32:
353
 
    from ctypes.wintypes import DWORD, LPWSTR
354
 
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p  # used as NULL no need to declare
355
 
    HANDLE = ctypes.c_int  # rather than unsigned as in ctypes.wintypes
356
 
    _function_name = "CreateFileW"
 
413
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
414
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
415
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
416
    if os.path.supports_unicode_filenames:
 
417
        _function_name = "CreateFileW"
 
418
        LPTSTR = LPCWSTR
 
419
    else:
 
420
        _function_name = "CreateFileA"
 
421
        class LPTSTR(LPCSTR):
 
422
            def __new__(cls, obj):
 
423
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
357
424
 
358
425
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
359
426
    _CreateFile = ctypes.WINFUNCTYPE(
360
 
        HANDLE,                # return value
361
 
        LPWSTR,                # lpFileName
362
 
        DWORD,                 # dwDesiredAccess
363
 
        DWORD,                 # dwShareMode
364
 
        LPSECURITY_ATTRIBUTES,  # lpSecurityAttributes
365
 
        DWORD,                 # dwCreationDisposition
366
 
        DWORD,                 # dwFlagsAndAttributes
367
 
        HANDLE                 # hTemplateFile
 
427
            HANDLE,                # return value
 
428
            LPTSTR,                # lpFileName
 
429
            DWORD,                 # dwDesiredAccess
 
430
            DWORD,                 # dwShareMode
 
431
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
432
            DWORD,                 # dwCreationDisposition
 
433
            DWORD,                 # dwFlagsAndAttributes
 
434
            HANDLE                 # hTemplateFile
368
435
        )((_function_name, ctypes.windll.kernel32))
369
436
 
370
437
    INVALID_HANDLE_VALUE = -1
383
450
        def _open(self, filename, access, share, cflags, pymode):
384
451
            self.filename = osutils.realpath(filename)
385
452
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
386
 
                                 FILE_ATTRIBUTE_NORMAL, 0)
 
453
                FILE_ATTRIBUTE_NORMAL, 0)
387
454
            if handle in (INVALID_HANDLE_VALUE, 0):
388
455
                e = ctypes.WinError()
389
456
                if e.args[0] == ERROR_ACCESS_DENIED:
398
465
        def unlock(self):
399
466
            self._clear_f()
400
467
 
 
468
 
401
469
    class _ctypes_ReadLock(_ctypes_FileLock):
402
470
        def __init__(self, filename):
403
471
            super(_ctypes_ReadLock, self).__init__()
404
472
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
405
 
                       "rb")
 
473
                "rb")
406
474
 
407
475
        def temporary_write_lock(self):
408
476
            """Try to grab a write lock on the file.
427
495
        def __init__(self, filename):
428
496
            super(_ctypes_WriteLock, self).__init__()
429
497
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
430
 
                       "rb+")
 
498
                "rb+")
431
499
 
432
500
        def restore_read_lock(self):
433
501
            """Restore the original ReadLock."""
436
504
            self.unlock()
437
505
            return _ctypes_ReadLock(self.filename)
438
506
 
 
507
 
439
508
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
440
509
 
441
510
 
442
511
if len(_lock_classes) == 0:
443
512
    raise NotImplementedError(
444
 
        "We must have one of fcntl or ctypes available"
 
513
        "We must have one of fcntl, pywin32, or ctypes available"
445
514
        " to support OS locking."
446
515
        )
447
516
 
458
527
    locked the same way), and -Drelock is set, then this will trace.note a
459
528
    message about it.
460
529
    """
461
 
 
 
530
    
462
531
    _prev_lock = None
463
532
 
464
533
    def _note_lock(self, lock_type):
467
536
                type_name = 'read'
468
537
            else:
469
538
                type_name = 'write'
470
 
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
539
            trace.note('%r was %s locked again', self, type_name)
471
540
        self._prev_lock = lock_type
472
541
 
473
 
 
474
 
@contextlib.contextmanager
475
 
def write_locked(lockable):
476
 
    lockable.lock_write()
477
 
    try:
478
 
        yield lockable
479
 
    finally:
480
 
        lockable.unlock()