/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: 2017-08-07 11:49:46 UTC
  • mto: (6747.3.4 avoid-set-revid-3)
  • mto: This revision was merged to the branch mainline in revision 6750.
  • Revision ID: jelmer@jelmer.uk-20170807114946-luclmxuawyzhpiot
Avoid setting revision_ids.

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):
 
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
 
 
146
 
 
147
class _OSLock(object):
 
148
 
 
149
    def __init__(self):
 
150
        self.f = None
 
151
        self.filename = None
 
152
 
 
153
    def _open(self, filename, filemode):
 
154
        self.filename = osutils.realpath(filename)
 
155
        try:
 
156
            self.f = open(self.filename, filemode)
 
157
            return self.f
 
158
        except IOError as e:
 
159
            if e.errno in (errno.EACCES, errno.EPERM):
 
160
                raise errors.LockFailed(self.filename, str(e))
 
161
            if e.errno != errno.ENOENT:
 
162
                raise
 
163
 
 
164
            # maybe this is an old branch (before may 2005)
 
165
            trace.mutter("trying to create missing lock %r", self.filename)
 
166
 
 
167
            self.f = open(self.filename, 'wb+')
 
168
            return self.f
 
169
 
 
170
    def _clear_f(self):
 
171
        """Clear the self.f attribute cleanly."""
 
172
        if self.f:
 
173
            self.f.close()
 
174
            self.f = None
 
175
 
 
176
    def unlock(self):
 
177
        raise NotImplementedError()
 
178
 
 
179
 
 
180
_lock_classes = []
 
181
 
 
182
 
 
183
if have_fcntl:
 
184
 
 
185
    class _fcntl_FileLock(_OSLock):
 
186
 
 
187
        def _unlock(self):
 
188
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
189
            self._clear_f()
 
190
 
 
191
 
 
192
    class _fcntl_WriteLock(_fcntl_FileLock):
 
193
 
 
194
        _open_locks = set()
 
195
 
 
196
        def __init__(self, filename):
 
197
            super(_fcntl_WriteLock, self).__init__()
 
198
            # Check we can grab a lock before we actually open the file.
 
199
            self.filename = osutils.realpath(filename)
 
200
            if self.filename in _fcntl_WriteLock._open_locks:
 
201
                self._clear_f()
 
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,))
 
210
 
 
211
            self._open(self.filename, 'rb+')
 
212
            # 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.
 
214
            # TODO: make this fully threadsafe, if we decide we care.
 
215
            _fcntl_WriteLock._open_locks.add(self.filename)
 
216
            try:
 
217
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
218
                # lock right away.
 
219
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
220
            except IOError as e:
 
221
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
222
                    # We couldn't grab the lock
 
223
                    self.unlock()
 
224
                # we should be more precise about whats a locking
 
225
                # error and whats a random-other error
 
226
                raise errors.LockContention(self.filename, e)
 
227
 
 
228
        def unlock(self):
 
229
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
230
            self._unlock()
 
231
 
 
232
 
 
233
    class _fcntl_ReadLock(_fcntl_FileLock):
 
234
 
 
235
        _open_locks = {}
 
236
 
 
237
        def __init__(self, filename):
 
238
            super(_fcntl_ReadLock, self).__init__()
 
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,))
 
248
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
249
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
250
            self._open(filename, 'rb')
 
251
            try:
 
252
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
253
                # lock right away.
 
254
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
255
            except IOError as e:
 
256
                # we should be more precise about whats a locking
 
257
                # error and whats a random-other error
 
258
                raise errors.LockContention(self.filename, e)
 
259
 
 
260
        def unlock(self):
 
261
            count = _fcntl_ReadLock._open_locks[self.filename]
 
262
            if count == 1:
 
263
                del _fcntl_ReadLock._open_locks[self.filename]
 
264
            else:
 
265
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
266
            self._unlock()
 
267
 
 
268
        def temporary_write_lock(self):
 
269
            """Try to grab a write lock on the file.
 
270
 
 
271
            On platforms that support it, this will upgrade to a write lock
 
272
            without unlocking the file.
 
273
            Otherwise, this will release the read lock, and try to acquire a
 
274
            write lock.
 
275
 
 
276
            :return: A token which can be used to switch back to a read lock.
 
277
            """
 
278
            if self.filename in _fcntl_WriteLock._open_locks:
 
279
                raise AssertionError('file already locked: %r'
 
280
                    % (self.filename,))
 
281
            try:
 
282
                wlock = _fcntl_TemporaryWriteLock(self)
 
283
            except errors.LockError:
 
284
                # We didn't unlock, so we can just return 'self'
 
285
                return False, self
 
286
            return True, wlock
 
287
 
 
288
 
 
289
    class _fcntl_TemporaryWriteLock(_OSLock):
 
290
        """A token used when grabbing a temporary_write_lock.
 
291
 
 
292
        Call restore_read_lock() when you are done with the write lock.
 
293
        """
 
294
 
 
295
        def __init__(self, read_lock):
 
296
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
297
            self._read_lock = read_lock
 
298
            self.filename = read_lock.filename
 
299
 
 
300
            count = _fcntl_ReadLock._open_locks[self.filename]
 
301
            if count > 1:
 
302
                # Something else also has a read-lock, so we cannot grab a
 
303
                # write lock.
 
304
                raise errors.LockContention(self.filename)
 
305
 
 
306
            if self.filename in _fcntl_WriteLock._open_locks:
 
307
                raise AssertionError('file already locked: %r'
 
308
                    % (self.filename,))
 
309
 
 
310
            # See if we can open the file for writing. Another process might
 
311
            # have a read lock. We don't use self._open() because we don't want
 
312
            # to create the file if it exists. That would have already been
 
313
            # done by _fcntl_ReadLock
 
314
            try:
 
315
                new_f = open(self.filename, 'rb+')
 
316
            except IOError as e:
 
317
                if e.errno in (errno.EACCES, errno.EPERM):
 
318
                    raise errors.LockFailed(self.filename, str(e))
 
319
                raise
 
320
            try:
 
321
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
322
                # lock right away.
 
323
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
324
            except IOError as e:
 
325
                # TODO: Raise a more specific error based on the type of error
 
326
                raise errors.LockContention(self.filename, e)
 
327
            _fcntl_WriteLock._open_locks.add(self.filename)
 
328
 
 
329
            self.f = new_f
 
330
 
 
331
        def restore_read_lock(self):
 
332
            """Restore the original ReadLock."""
 
333
            # For fcntl, since we never released the read lock, just release the
 
334
            # write lock, and return the original lock.
 
335
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
336
            self._clear_f()
 
337
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
338
            # Avoid reference cycles
 
339
            read_lock = self._read_lock
 
340
            self._read_lock = None
 
341
            return read_lock
 
342
 
 
343
 
 
344
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
345
 
 
346
 
 
347
if have_pywin32 and sys.platform == 'win32':
 
348
    win32file_CreateFile = win32file.CreateFileW
 
349
 
 
350
    class _w32c_FileLock(_OSLock):
 
351
 
 
352
        def _open(self, filename, access, share, cflags, pymode):
 
353
            self.filename = osutils.realpath(filename)
 
354
            try:
 
355
                self._handle = win32file_CreateFile(filename, access, share,
 
356
                    None, win32file.OPEN_ALWAYS,
 
357
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
358
            except pywintypes.error as e:
 
359
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
360
                    raise errors.LockFailed(filename, e)
 
361
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
362
                    raise errors.LockContention(filename, e)
 
363
                raise
 
364
            fd = win32file._open_osfhandle(self._handle, cflags)
 
365
            self.f = os.fdopen(fd, pymode)
 
366
            return self.f
 
367
 
 
368
        def unlock(self):
 
369
            self._clear_f()
 
370
            self._handle = None
 
371
 
 
372
 
 
373
    class _w32c_ReadLock(_w32c_FileLock):
 
374
        def __init__(self, filename):
 
375
            super(_w32c_ReadLock, self).__init__()
 
376
            self._open(filename, win32file.GENERIC_READ,
 
377
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
378
 
 
379
        def temporary_write_lock(self):
 
380
            """Try to grab a write lock on the file.
 
381
 
 
382
            On platforms that support it, this will upgrade to a write lock
 
383
            without unlocking the file.
 
384
            Otherwise, this will release the read lock, and try to acquire a
 
385
            write lock.
 
386
 
 
387
            :return: A token which can be used to switch back to a read lock.
 
388
            """
 
389
            # I can't find a way to upgrade a read lock to a write lock without
 
390
            # unlocking first. So here, we do just that.
 
391
            self.unlock()
 
392
            try:
 
393
                wlock = _w32c_WriteLock(self.filename)
 
394
            except errors.LockError:
 
395
                return False, _w32c_ReadLock(self.filename)
 
396
            return True, wlock
 
397
 
 
398
 
 
399
    class _w32c_WriteLock(_w32c_FileLock):
 
400
        def __init__(self, filename):
 
401
            super(_w32c_WriteLock, self).__init__()
 
402
            self._open(filename,
 
403
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
404
                os.O_RDWR, "rb+")
 
405
 
 
406
        def restore_read_lock(self):
 
407
            """Restore the original ReadLock."""
 
408
            # For win32 we had to completely let go of the original lock, so we
 
409
            # just unlock and create a new read lock.
 
410
            self.unlock()
 
411
            return _w32c_ReadLock(self.filename)
 
412
 
 
413
 
 
414
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
415
 
 
416
 
 
417
if have_ctypes_win32:
 
418
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
419
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
420
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
421
    _function_name = "CreateFileW"
 
422
 
 
423
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
424
    _CreateFile = ctypes.WINFUNCTYPE(
 
425
            HANDLE,                # return value
 
426
            LPWSTR,                # lpFileName
 
427
            DWORD,                 # dwDesiredAccess
 
428
            DWORD,                 # dwShareMode
 
429
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
430
            DWORD,                 # dwCreationDisposition
 
431
            DWORD,                 # dwFlagsAndAttributes
 
432
            HANDLE                 # hTemplateFile
 
433
        )((_function_name, ctypes.windll.kernel32))
 
434
 
 
435
    INVALID_HANDLE_VALUE = -1
 
436
 
 
437
    GENERIC_READ = 0x80000000
 
438
    GENERIC_WRITE = 0x40000000
 
439
    FILE_SHARE_READ = 1
 
440
    OPEN_ALWAYS = 4
 
441
    FILE_ATTRIBUTE_NORMAL = 128
 
442
 
 
443
    ERROR_ACCESS_DENIED = 5
 
444
    ERROR_SHARING_VIOLATION = 32
 
445
 
 
446
    class _ctypes_FileLock(_OSLock):
 
447
 
 
448
        def _open(self, filename, access, share, cflags, pymode):
 
449
            self.filename = osutils.realpath(filename)
 
450
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
 
451
                FILE_ATTRIBUTE_NORMAL, 0)
 
452
            if handle in (INVALID_HANDLE_VALUE, 0):
 
453
                e = ctypes.WinError()
 
454
                if e.args[0] == ERROR_ACCESS_DENIED:
 
455
                    raise errors.LockFailed(filename, e)
 
456
                if e.args[0] == ERROR_SHARING_VIOLATION:
 
457
                    raise errors.LockContention(filename, e)
 
458
                raise e
 
459
            fd = msvcrt.open_osfhandle(handle, cflags)
 
460
            self.f = os.fdopen(fd, pymode)
 
461
            return self.f
 
462
 
 
463
        def unlock(self):
 
464
            self._clear_f()
 
465
 
 
466
 
 
467
    class _ctypes_ReadLock(_ctypes_FileLock):
 
468
        def __init__(self, filename):
 
469
            super(_ctypes_ReadLock, self).__init__()
 
470
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
471
                "rb")
 
472
 
 
473
        def temporary_write_lock(self):
 
474
            """Try to grab a write lock on the file.
 
475
 
 
476
            On platforms that support it, this will upgrade to a write lock
 
477
            without unlocking the file.
 
478
            Otherwise, this will release the read lock, and try to acquire a
 
479
            write lock.
 
480
 
 
481
            :return: A token which can be used to switch back to a read lock.
 
482
            """
 
483
            # I can't find a way to upgrade a read lock to a write lock without
 
484
            # unlocking first. So here, we do just that.
 
485
            self.unlock()
 
486
            try:
 
487
                wlock = _ctypes_WriteLock(self.filename)
 
488
            except errors.LockError:
 
489
                return False, _ctypes_ReadLock(self.filename)
 
490
            return True, wlock
 
491
 
 
492
    class _ctypes_WriteLock(_ctypes_FileLock):
 
493
        def __init__(self, filename):
 
494
            super(_ctypes_WriteLock, self).__init__()
 
495
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
496
                "rb+")
 
497
 
 
498
        def restore_read_lock(self):
 
499
            """Restore the original ReadLock."""
 
500
            # For win32 we had to completely let go of the original lock, so we
 
501
            # just unlock and create a new read lock.
 
502
            self.unlock()
 
503
            return _ctypes_ReadLock(self.filename)
 
504
 
 
505
 
 
506
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
507
 
 
508
 
 
509
if len(_lock_classes) == 0:
 
510
    raise NotImplementedError(
 
511
        "We must have one of fcntl, pywin32, or ctypes available"
 
512
        " to support OS locking."
 
513
        )
 
514
 
 
515
 
 
516
# We default to using the first available lock class.
 
517
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
518
 
 
519
 
 
520
class _RelockDebugMixin(object):
 
521
    """Mixin support for -Drelock flag.
 
522
 
 
523
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
524
    acquiring a read- or write-lock.  If this object was previously locked (and
 
525
    locked the same way), and -Drelock is set, then this will trace.note a
 
526
    message about it.
 
527
    """
 
528
 
 
529
    _prev_lock = None
 
530
 
 
531
    def _note_lock(self, lock_type):
 
532
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
533
            if lock_type == 'r':
 
534
                type_name = 'read'
 
535
            else:
 
536
                type_name = 'write'
 
537
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
538
        self._prev_lock = lock_type
 
539
 
 
540
@contextlib.contextmanager
 
541
def write_locked(lockable):
 
542
    lockable.lock_write()
 
543
    try:
 
544
        yield lockable
 
545
    finally:
 
546
        lockable.unlock()