/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 breezy/lock.py

  • Committer: Jelmer Vernooij
  • Date: 2018-05-07 15:27:39 UTC
  • mto: This revision was merged to the branch mainline in revision 6958.
  • Revision ID: jelmer@jelmer.uk-20180507152739-fuv9z9r0yzi7ln3t
Specify source in .coveragerc.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
"""Locking using OS file locks or file existence.
 
18
 
 
19
Note: This method of locking is generally deprecated in favour of LockDir, but
 
20
is used to lock local WorkingTrees, and by some old formats.  It's accessed
 
21
through Transport.lock_read(), etc.
 
22
 
 
23
This module causes two methods, lock() and unlock() to be defined in
 
24
any way that works on the current platform.
 
25
 
 
26
It is not specified whether these locks are reentrant (i.e. can be
 
27
taken repeatedly by a single process) or whether they exclude
 
28
different threads in a single process.  That reentrancy is provided by
 
29
LockableFiles.
 
30
 
 
31
This defines two classes: ReadLock and WriteLock, which can be
 
32
implemented in different ways on different platforms.  Both have an
 
33
unlock() method.
 
34
"""
 
35
 
 
36
from __future__ import absolute_import
 
37
 
 
38
import contextlib
 
39
import errno
 
40
import os
 
41
import sys
 
42
import warnings
 
43
 
 
44
from . import (
 
45
    debug,
 
46
    errors,
 
47
    osutils,
 
48
    trace,
 
49
    )
 
50
from .hooks import Hooks
 
51
from .i18n import gettext
 
52
 
 
53
class LockHooks(Hooks):
 
54
 
 
55
    def __init__(self):
 
56
        Hooks.__init__(self, "breezy.lock", "Lock.hooks")
 
57
        self.add_hook('lock_acquired',
 
58
            "Called with a breezy.lock.LockResult when a physical lock is "
 
59
            "acquired.", (1, 8))
 
60
        self.add_hook('lock_released',
 
61
            "Called with a breezy.lock.LockResult when a physical lock is "
 
62
            "released.", (1, 8))
 
63
        self.add_hook('lock_broken',
 
64
            "Called with a breezy.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, token=None):
 
100
        self.unlock = unlock
 
101
        self.token = token
 
102
 
 
103
    def __repr__(self):
 
104
        return "LogicalLockResult(%s)" % (self.unlock)
 
105
 
 
106
    def __enter__(self):
 
107
        return self
 
108
 
 
109
    def __exit__(self, exc_type, exc_val, exc_tb):
 
110
        # If there was an error raised, prefer the original one
 
111
        try:
 
112
            self.unlock()
 
113
        except:
 
114
            if exc_type is None:
 
115
                raise
 
116
        return False
 
117
 
 
118
 
 
119
def cant_unlock_not_held(locked_object):
 
120
    """An attempt to unlock failed because the object was not locked.
 
121
 
 
122
    This provides a policy point from which we can generate either a warning 
 
123
    or an exception.
 
124
    """
 
125
    # This is typically masking some other error and called from a finally
 
126
    # block, so it's useful to have the option not to generate a new error
 
127
    # here.  You can use -Werror to make it fatal.  It should possibly also
 
128
    # raise LockNotHeld.
 
129
    if 'unlock' in debug.debug_flags:
 
130
        warnings.warn("%r is already unlocked" % (locked_object,),
 
131
            stacklevel=3)
 
132
    else:
 
133
        raise errors.LockNotHeld(locked_object)
 
134
 
 
135
 
 
136
try:
 
137
    import fcntl
 
138
    have_fcntl = True
 
139
except ImportError:
 
140
    have_fcntl = False
 
141
 
 
142
have_pywin32 = False
 
143
have_ctypes_win32 = False
 
144
if sys.platform == 'win32':
 
145
    import msvcrt
 
146
    try:
 
147
        import win32file, pywintypes, winerror
 
148
        have_pywin32 = True
 
149
    except ImportError:
 
150
        pass
 
151
 
 
152
    try:
 
153
        import ctypes
 
154
        have_ctypes_win32 = True
 
155
    except ImportError:
 
156
        pass
 
157
 
 
158
 
 
159
class _OSLock(object):
 
160
 
 
161
    def __init__(self):
 
162
        self.f = None
 
163
        self.filename = None
 
164
 
 
165
    def _open(self, filename, filemode):
 
166
        self.filename = osutils.realpath(filename)
 
167
        try:
 
168
            self.f = open(self.filename, filemode)
 
169
            return self.f
 
170
        except IOError as e:
 
171
            if e.errno in (errno.EACCES, errno.EPERM):
 
172
                raise errors.LockFailed(self.filename, str(e))
 
173
            if e.errno != errno.ENOENT:
 
174
                raise
 
175
 
 
176
            # maybe this is an old branch (before may 2005)
 
177
            trace.mutter("trying to create missing lock %r", self.filename)
 
178
 
 
179
            self.f = open(self.filename, 'wb+')
 
180
            return self.f
 
181
 
 
182
    def _clear_f(self):
 
183
        """Clear the self.f attribute cleanly."""
 
184
        if self.f:
 
185
            self.f.close()
 
186
            self.f = None
 
187
 
 
188
    def unlock(self):
 
189
        raise NotImplementedError()
 
190
 
 
191
 
 
192
_lock_classes = []
 
193
 
 
194
 
 
195
if have_fcntl:
 
196
 
 
197
    class _fcntl_FileLock(_OSLock):
 
198
 
 
199
        def _unlock(self):
 
200
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
201
            self._clear_f()
 
202
 
 
203
 
 
204
    class _fcntl_WriteLock(_fcntl_FileLock):
 
205
 
 
206
        _open_locks = set()
 
207
 
 
208
        def __init__(self, filename):
 
209
            super(_fcntl_WriteLock, self).__init__()
 
210
            # Check we can grab a lock before we actually open the file.
 
211
            self.filename = osutils.realpath(filename)
 
212
            if self.filename in _fcntl_WriteLock._open_locks:
 
213
                self._clear_f()
 
214
                raise errors.LockContention(self.filename)
 
215
            if self.filename in _fcntl_ReadLock._open_locks:
 
216
                if 'strict_locks' in debug.debug_flags:
 
217
                    self._clear_f()
 
218
                    raise errors.LockContention(self.filename)
 
219
                else:
 
220
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
221
                                 % (self.filename,))
 
222
 
 
223
            self._open(self.filename, 'rb+')
 
224
            # reserve a slot for this lock - even if the lockf call fails,
 
225
            # at this point unlock() will be called, because self.f is set.
 
226
            # TODO: make this fully threadsafe, if we decide we care.
 
227
            _fcntl_WriteLock._open_locks.add(self.filename)
 
228
            try:
 
229
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
230
                # lock right away.
 
231
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
232
            except IOError as e:
 
233
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
234
                    # We couldn't grab the lock
 
235
                    self.unlock()
 
236
                # we should be more precise about whats a locking
 
237
                # error and whats a random-other error
 
238
                raise errors.LockContention(self.filename, e)
 
239
 
 
240
        def unlock(self):
 
241
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
242
            self._unlock()
 
243
 
 
244
 
 
245
    class _fcntl_ReadLock(_fcntl_FileLock):
 
246
 
 
247
        _open_locks = {}
 
248
 
 
249
        def __init__(self, filename):
 
250
            super(_fcntl_ReadLock, self).__init__()
 
251
            self.filename = osutils.realpath(filename)
 
252
            if self.filename in _fcntl_WriteLock._open_locks:
 
253
                if 'strict_locks' in debug.debug_flags:
 
254
                    # We raise before calling _open so we don't need to
 
255
                    # _clear_f
 
256
                    raise errors.LockContention(self.filename)
 
257
                else:
 
258
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
259
                                 % (self.filename,))
 
260
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
261
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
262
            self._open(filename, 'rb')
 
263
            try:
 
264
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
265
                # lock right away.
 
266
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
267
            except IOError as e:
 
268
                # we should be more precise about whats a locking
 
269
                # error and whats a random-other error
 
270
                raise errors.LockContention(self.filename, e)
 
271
 
 
272
        def unlock(self):
 
273
            count = _fcntl_ReadLock._open_locks[self.filename]
 
274
            if count == 1:
 
275
                del _fcntl_ReadLock._open_locks[self.filename]
 
276
            else:
 
277
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
278
            self._unlock()
 
279
 
 
280
        def temporary_write_lock(self):
 
281
            """Try to grab a write lock on the file.
 
282
 
 
283
            On platforms that support it, this will upgrade to a write lock
 
284
            without unlocking the file.
 
285
            Otherwise, this will release the read lock, and try to acquire a
 
286
            write lock.
 
287
 
 
288
            :return: A token which can be used to switch back to a read lock.
 
289
            """
 
290
            if self.filename in _fcntl_WriteLock._open_locks:
 
291
                raise AssertionError('file already locked: %r'
 
292
                    % (self.filename,))
 
293
            try:
 
294
                wlock = _fcntl_TemporaryWriteLock(self)
 
295
            except errors.LockError:
 
296
                # We didn't unlock, so we can just return 'self'
 
297
                return False, self
 
298
            return True, wlock
 
299
 
 
300
 
 
301
    class _fcntl_TemporaryWriteLock(_OSLock):
 
302
        """A token used when grabbing a temporary_write_lock.
 
303
 
 
304
        Call restore_read_lock() when you are done with the write lock.
 
305
        """
 
306
 
 
307
        def __init__(self, read_lock):
 
308
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
309
            self._read_lock = read_lock
 
310
            self.filename = read_lock.filename
 
311
 
 
312
            count = _fcntl_ReadLock._open_locks[self.filename]
 
313
            if count > 1:
 
314
                # Something else also has a read-lock, so we cannot grab a
 
315
                # write lock.
 
316
                raise errors.LockContention(self.filename)
 
317
 
 
318
            if self.filename in _fcntl_WriteLock._open_locks:
 
319
                raise AssertionError('file already locked: %r'
 
320
                    % (self.filename,))
 
321
 
 
322
            # See if we can open the file for writing. Another process might
 
323
            # have a read lock. We don't use self._open() because we don't want
 
324
            # to create the file if it exists. That would have already been
 
325
            # done by _fcntl_ReadLock
 
326
            try:
 
327
                new_f = open(self.filename, 'rb+')
 
328
            except IOError as e:
 
329
                if e.errno in (errno.EACCES, errno.EPERM):
 
330
                    raise errors.LockFailed(self.filename, str(e))
 
331
                raise
 
332
            try:
 
333
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
334
                # lock right away.
 
335
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
336
            except IOError as e:
 
337
                # TODO: Raise a more specific error based on the type of error
 
338
                raise errors.LockContention(self.filename, e)
 
339
            _fcntl_WriteLock._open_locks.add(self.filename)
 
340
 
 
341
            self.f = new_f
 
342
 
 
343
        def restore_read_lock(self):
 
344
            """Restore the original ReadLock."""
 
345
            # For fcntl, since we never released the read lock, just release the
 
346
            # write lock, and return the original lock.
 
347
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
348
            self._clear_f()
 
349
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
350
            # Avoid reference cycles
 
351
            read_lock = self._read_lock
 
352
            self._read_lock = None
 
353
            return read_lock
 
354
 
 
355
 
 
356
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
357
 
 
358
 
 
359
if have_pywin32 and sys.platform == 'win32':
 
360
    win32file_CreateFile = win32file.CreateFileW
 
361
 
 
362
    class _w32c_FileLock(_OSLock):
 
363
 
 
364
        def _open(self, filename, access, share, cflags, pymode):
 
365
            self.filename = osutils.realpath(filename)
 
366
            try:
 
367
                self._handle = win32file_CreateFile(filename, access, share,
 
368
                    None, win32file.OPEN_ALWAYS,
 
369
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
370
            except pywintypes.error as e:
 
371
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
372
                    raise errors.LockFailed(filename, e)
 
373
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
374
                    raise errors.LockContention(filename, e)
 
375
                raise
 
376
            fd = win32file._open_osfhandle(self._handle, cflags)
 
377
            self.f = os.fdopen(fd, pymode)
 
378
            return self.f
 
379
 
 
380
        def unlock(self):
 
381
            self._clear_f()
 
382
            self._handle = None
 
383
 
 
384
 
 
385
    class _w32c_ReadLock(_w32c_FileLock):
 
386
        def __init__(self, filename):
 
387
            super(_w32c_ReadLock, self).__init__()
 
388
            self._open(filename, win32file.GENERIC_READ,
 
389
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
390
 
 
391
        def temporary_write_lock(self):
 
392
            """Try to grab a write lock on the file.
 
393
 
 
394
            On platforms that support it, this will upgrade to a write lock
 
395
            without unlocking the file.
 
396
            Otherwise, this will release the read lock, and try to acquire a
 
397
            write lock.
 
398
 
 
399
            :return: A token which can be used to switch back to a read lock.
 
400
            """
 
401
            # I can't find a way to upgrade a read lock to a write lock without
 
402
            # unlocking first. So here, we do just that.
 
403
            self.unlock()
 
404
            try:
 
405
                wlock = _w32c_WriteLock(self.filename)
 
406
            except errors.LockError:
 
407
                return False, _w32c_ReadLock(self.filename)
 
408
            return True, wlock
 
409
 
 
410
 
 
411
    class _w32c_WriteLock(_w32c_FileLock):
 
412
        def __init__(self, filename):
 
413
            super(_w32c_WriteLock, self).__init__()
 
414
            self._open(filename,
 
415
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
416
                os.O_RDWR, "rb+")
 
417
 
 
418
        def restore_read_lock(self):
 
419
            """Restore the original ReadLock."""
 
420
            # For win32 we had to completely let go of the original lock, so we
 
421
            # just unlock and create a new read lock.
 
422
            self.unlock()
 
423
            return _w32c_ReadLock(self.filename)
 
424
 
 
425
 
 
426
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
427
 
 
428
 
 
429
if have_ctypes_win32:
 
430
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
431
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
432
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
433
    _function_name = "CreateFileW"
 
434
 
 
435
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
436
    _CreateFile = ctypes.WINFUNCTYPE(
 
437
            HANDLE,                # return value
 
438
            LPWSTR,                # 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
 
457
 
 
458
    class _ctypes_FileLock(_OSLock):
 
459
 
 
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
 
474
 
 
475
        def unlock(self):
 
476
            self._clear_f()
 
477
 
 
478
 
 
479
    class _ctypes_ReadLock(_ctypes_FileLock):
 
480
        def __init__(self, filename):
 
481
            super(_ctypes_ReadLock, self).__init__()
 
482
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
483
                "rb")
 
484
 
 
485
        def temporary_write_lock(self):
 
486
            """Try to grab a write lock on the file.
 
487
 
 
488
            On platforms that support it, this will upgrade to a write lock
 
489
            without unlocking the file.
 
490
            Otherwise, this will release the read lock, and try to acquire a
 
491
            write lock.
 
492
 
 
493
            :return: A token which can be used to switch back to a read lock.
 
494
            """
 
495
            # I can't find a way to upgrade a read lock to a write lock without
 
496
            # unlocking first. So here, we do just that.
 
497
            self.unlock()
 
498
            try:
 
499
                wlock = _ctypes_WriteLock(self.filename)
 
500
            except errors.LockError:
 
501
                return False, _ctypes_ReadLock(self.filename)
 
502
            return True, wlock
 
503
 
 
504
    class _ctypes_WriteLock(_ctypes_FileLock):
 
505
        def __init__(self, filename):
 
506
            super(_ctypes_WriteLock, self).__init__()
 
507
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
508
                "rb+")
 
509
 
 
510
        def restore_read_lock(self):
 
511
            """Restore the original ReadLock."""
 
512
            # For win32 we had to completely let go of the original lock, so we
 
513
            # just unlock and create a new read lock.
 
514
            self.unlock()
 
515
            return _ctypes_ReadLock(self.filename)
 
516
 
 
517
 
 
518
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
519
 
 
520
 
 
521
if len(_lock_classes) == 0:
 
522
    raise NotImplementedError(
 
523
        "We must have one of fcntl, pywin32, or ctypes available"
 
524
        " to support OS locking."
 
525
        )
 
526
 
 
527
 
 
528
# We default to using the first available lock class.
 
529
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
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()