/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: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

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
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
 
import contextlib
37
37
import errno
38
38
import os
39
39
import sys
40
40
import warnings
41
41
 
42
 
from . import (
 
42
from bzrlib import (
43
43
    debug,
44
44
    errors,
45
45
    osutils,
46
46
    trace,
47
47
    )
48
 
from .hooks import Hooks
49
 
from .i18n import gettext
 
48
from bzrlib.hooks import HookPoint, Hooks
50
49
 
51
50
 
52
51
class LockHooks(Hooks):
53
52
 
54
53
    def __init__(self):
55
 
        Hooks.__init__(self, "breezy.lock", "Lock.hooks")
56
 
        self.add_hook(
57
 
            'lock_acquired',
58
 
            "Called with a breezy.lock.LockResult when a physical lock is "
59
 
            "acquired.", (1, 8))
60
 
        self.add_hook(
61
 
            'lock_released',
62
 
            "Called with a breezy.lock.LockResult when a physical lock is "
63
 
            "released.", (1, 8))
64
 
        self.add_hook(
65
 
            'lock_broken',
66
 
            "Called with a breezy.lock.LockResult when a physical lock is "
67
 
            "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))
68
64
 
69
65
 
70
66
class Lock(object):
88
84
        return self.lock_url == other.lock_url and self.details == other.details
89
85
 
90
86
    def __repr__(self):
91
 
        return '%s(%s, %s)' % (self.__class__.__name__,
92
 
                               self.lock_url, self.details)
93
 
 
94
 
 
95
 
class LogicalLockResult(object):
96
 
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
97
 
 
98
 
    :ivar unlock: A callable which will unlock the lock.
99
 
    """
100
 
 
101
 
    def __init__(self, unlock, token=None):
102
 
        self.unlock = unlock
103
 
        self.token = token
104
 
 
105
 
    def __repr__(self):
106
 
        return "LogicalLockResult(%s)" % (self.unlock)
107
 
 
108
 
    def __enter__(self):
109
 
        return self
110
 
 
111
 
    def __exit__(self, exc_type, exc_val, exc_tb):
112
 
        # If there was an error raised, prefer the original one
113
 
        try:
114
 
            self.unlock()
115
 
        except BaseException:
116
 
            if exc_type is None:
117
 
                raise
118
 
        return False
 
87
        return '%s(%s%s)' % (self.__class__.__name__,
 
88
                             self.lock_url, self.details)
119
89
 
120
90
 
121
91
def cant_unlock_not_held(locked_object):
122
92
    """An attempt to unlock failed because the object was not locked.
123
93
 
124
 
    This provides a policy point from which we can generate either a warning or
125
 
    an exception.
 
94
    This provides a policy point from which we can generate either a warning 
 
95
    or an exception.
126
96
    """
127
97
    # This is typically masking some other error and called from a finally
128
98
    # block, so it's useful to have the option not to generate a new error
130
100
    # raise LockNotHeld.
131
101
    if 'unlock' in debug.debug_flags:
132
102
        warnings.warn("%r is already unlocked" % (locked_object,),
133
 
                      stacklevel=3)
 
103
            stacklevel=3)
134
104
    else:
135
105
        raise errors.LockNotHeld(locked_object)
136
106
 
141
111
except ImportError:
142
112
    have_fcntl = False
143
113
 
 
114
have_pywin32 = False
144
115
have_ctypes_win32 = False
145
116
if sys.platform == 'win32':
146
117
    import msvcrt
147
118
    try:
 
119
        import win32file, pywintypes, winerror
 
120
        have_pywin32 = True
 
121
    except ImportError:
 
122
        pass
 
123
 
 
124
    try:
148
125
        import ctypes
149
126
        have_ctypes_win32 = True
150
127
    except ImportError:
162
139
        try:
163
140
            self.f = open(self.filename, filemode)
164
141
            return self.f
165
 
        except IOError as e:
 
142
        except IOError, e:
166
143
            if e.errno in (errno.EACCES, errno.EPERM):
167
144
                raise errors.LockFailed(self.filename, str(e))
168
145
            if e.errno != errno.ENOENT:
180
157
            self.f.close()
181
158
            self.f = None
182
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
 
183
166
    def unlock(self):
184
167
        raise NotImplementedError()
185
168
 
195
178
            fcntl.lockf(self.f, fcntl.LOCK_UN)
196
179
            self._clear_f()
197
180
 
 
181
 
198
182
    class _fcntl_WriteLock(_fcntl_FileLock):
199
183
 
200
184
        _open_locks = set()
216
200
 
217
201
            self._open(self.filename, 'rb+')
218
202
            # reserve a slot for this lock - even if the lockf call fails,
219
 
            # at this point unlock() will be called, because self.f is set.
 
203
            # at thisi point unlock() will be called, because self.f is set.
220
204
            # TODO: make this fully threadsafe, if we decide we care.
221
205
            _fcntl_WriteLock._open_locks.add(self.filename)
222
206
            try:
223
207
                # LOCK_NB will cause IOError to be raised if we can't grab a
224
208
                # lock right away.
225
209
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
226
 
            except IOError as e:
 
210
            except IOError, e:
227
211
                if e.errno in (errno.EAGAIN, errno.EACCES):
228
212
                    # We couldn't grab the lock
229
213
                    self.unlock()
235
219
            _fcntl_WriteLock._open_locks.remove(self.filename)
236
220
            self._unlock()
237
221
 
 
222
 
238
223
    class _fcntl_ReadLock(_fcntl_FileLock):
239
224
 
240
225
        _open_locks = {}
257
242
                # LOCK_NB will cause IOError to be raised if we can't grab a
258
243
                # lock right away.
259
244
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
260
 
            except IOError as e:
 
245
            except IOError, e:
261
246
                # we should be more precise about whats a locking
262
247
                # error and whats a random-other error
263
248
                raise errors.LockContention(self.filename, e)
282
267
            """
283
268
            if self.filename in _fcntl_WriteLock._open_locks:
284
269
                raise AssertionError('file already locked: %r'
285
 
                                     % (self.filename,))
 
270
                    % (self.filename,))
286
271
            try:
287
272
                wlock = _fcntl_TemporaryWriteLock(self)
288
273
            except errors.LockError:
290
275
                return False, self
291
276
            return True, wlock
292
277
 
 
278
 
293
279
    class _fcntl_TemporaryWriteLock(_OSLock):
294
280
        """A token used when grabbing a temporary_write_lock.
295
281
 
309
295
 
310
296
            if self.filename in _fcntl_WriteLock._open_locks:
311
297
                raise AssertionError('file already locked: %r'
312
 
                                     % (self.filename,))
 
298
                    % (self.filename,))
313
299
 
314
300
            # See if we can open the file for writing. Another process might
315
301
            # have a read lock. We don't use self._open() because we don't want
317
303
            # done by _fcntl_ReadLock
318
304
            try:
319
305
                new_f = open(self.filename, 'rb+')
320
 
            except IOError as e:
 
306
            except IOError, e:
321
307
                if e.errno in (errno.EACCES, errno.EPERM):
322
308
                    raise errors.LockFailed(self.filename, str(e))
323
309
                raise
325
311
                # LOCK_NB will cause IOError to be raised if we can't grab a
326
312
                # lock right away.
327
313
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
328
 
            except IOError as e:
 
314
            except IOError, e:
329
315
                # TODO: Raise a more specific error based on the type of error
330
316
                raise errors.LockContention(self.filename, e)
331
317
            _fcntl_WriteLock._open_locks.add(self.filename)
334
320
 
335
321
        def restore_read_lock(self):
336
322
            """Restore the original ReadLock."""
337
 
            # For fcntl, since we never released the read lock, just release
338
 
            # 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.
339
325
            fcntl.lockf(self.f, fcntl.LOCK_UN)
340
326
            self._clear_f()
341
327
            _fcntl_WriteLock._open_locks.remove(self.filename)
344
330
            self._read_lock = None
345
331
            return read_lock
346
332
 
 
333
 
347
334
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
348
335
 
349
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
 
350
412
if have_ctypes_win32:
351
 
    from ctypes.wintypes import DWORD, LPWSTR
352
 
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p  # used as NULL no need to declare
353
 
    HANDLE = ctypes.c_int  # rather than unsigned as in ctypes.wintypes
354
 
    _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"))
355
424
 
356
425
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
357
426
    _CreateFile = ctypes.WINFUNCTYPE(
358
 
        HANDLE,                # return value
359
 
        LPWSTR,                # lpFileName
360
 
        DWORD,                 # dwDesiredAccess
361
 
        DWORD,                 # dwShareMode
362
 
        LPSECURITY_ATTRIBUTES,  # lpSecurityAttributes
363
 
        DWORD,                 # dwCreationDisposition
364
 
        DWORD,                 # dwFlagsAndAttributes
365
 
        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
366
435
        )((_function_name, ctypes.windll.kernel32))
367
436
 
368
437
    INVALID_HANDLE_VALUE = -1
381
450
        def _open(self, filename, access, share, cflags, pymode):
382
451
            self.filename = osutils.realpath(filename)
383
452
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
384
 
                                 FILE_ATTRIBUTE_NORMAL, 0)
 
453
                FILE_ATTRIBUTE_NORMAL, 0)
385
454
            if handle in (INVALID_HANDLE_VALUE, 0):
386
455
                e = ctypes.WinError()
387
456
                if e.args[0] == ERROR_ACCESS_DENIED:
396
465
        def unlock(self):
397
466
            self._clear_f()
398
467
 
 
468
 
399
469
    class _ctypes_ReadLock(_ctypes_FileLock):
400
470
        def __init__(self, filename):
401
471
            super(_ctypes_ReadLock, self).__init__()
402
472
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
403
 
                       "rb")
 
473
                "rb")
404
474
 
405
475
        def temporary_write_lock(self):
406
476
            """Try to grab a write lock on the file.
425
495
        def __init__(self, filename):
426
496
            super(_ctypes_WriteLock, self).__init__()
427
497
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
428
 
                       "rb+")
 
498
                "rb+")
429
499
 
430
500
        def restore_read_lock(self):
431
501
            """Restore the original ReadLock."""
434
504
            self.unlock()
435
505
            return _ctypes_ReadLock(self.filename)
436
506
 
 
507
 
437
508
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
438
509
 
439
510
 
440
511
if len(_lock_classes) == 0:
441
512
    raise NotImplementedError(
442
 
        "We must have one of fcntl or ctypes available"
 
513
        "We must have one of fcntl, pywin32, or ctypes available"
443
514
        " to support OS locking."
444
515
        )
445
516
 
447
518
# We default to using the first available lock class.
448
519
_lock_type, WriteLock, ReadLock = _lock_classes[0]
449
520
 
450
 
 
451
 
class _RelockDebugMixin(object):
452
 
    """Mixin support for -Drelock flag.
453
 
 
454
 
    Add this as a base class then call self._note_lock with 'r' or 'w' when
455
 
    acquiring a read- or write-lock.  If this object was previously locked (and
456
 
    locked the same way), and -Drelock is set, then this will trace.note a
457
 
    message about it.
458
 
    """
459
 
 
460
 
    _prev_lock = None
461
 
 
462
 
    def _note_lock(self, lock_type):
463
 
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
464
 
            if lock_type == 'r':
465
 
                type_name = 'read'
466
 
            else:
467
 
                type_name = 'write'
468
 
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
469
 
        self._prev_lock = lock_type
470
 
 
471
 
 
472
 
@contextlib.contextmanager
473
 
def write_locked(lockable):
474
 
    lockable.lock_write()
475
 
    try:
476
 
        yield lockable
477
 
    finally:
478
 
        lockable.unlock()