~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

Merge bzr.dev and tree-file-ids-as-tuples.

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 errno
 
39
import os
 
40
import sys
 
41
import warnings
 
42
 
 
43
from bzrlib import (
 
44
    debug,
 
45
    errors,
 
46
    osutils,
 
47
    trace,
 
48
    )
 
49
from bzrlib.hooks import Hooks
 
50
from bzrlib.i18n import gettext
 
51
 
 
52
class LockHooks(Hooks):
 
53
 
 
54
    def __init__(self):
 
55
        Hooks.__init__(self, "bzrlib.lock", "Lock.hooks")
 
56
        self.add_hook('lock_acquired',
 
57
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
58
            "acquired.", (1, 8))
 
59
        self.add_hook('lock_released',
 
60
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
61
            "released.", (1, 8))
 
62
        self.add_hook('lock_broken',
 
63
            "Called with a bzrlib.lock.LockResult when a physical lock is "
 
64
            "broken.", (1, 15))
 
65
 
 
66
 
 
67
class Lock(object):
 
68
    """Base class for locks.
 
69
 
 
70
    :cvar hooks: Hook dictionary for operations on locks.
 
71
    """
 
72
 
 
73
    hooks = LockHooks()
 
74
 
 
75
 
 
76
class LockResult(object):
 
77
    """Result of an operation on a lock; passed to a hook"""
 
78
 
 
79
    def __init__(self, lock_url, details=None):
 
80
        """Create a lock result for lock with optional details about the lock."""
 
81
        self.lock_url = lock_url
 
82
        self.details = details
 
83
 
 
84
    def __eq__(self, other):
 
85
        return self.lock_url == other.lock_url and self.details == other.details
 
86
 
 
87
    def __repr__(self):
 
88
        return '%s(%s, %s)' % (self.__class__.__name__,
 
89
                             self.lock_url, self.details)
 
90
 
 
91
 
 
92
class LogicalLockResult(object):
 
93
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
 
94
 
 
95
    :ivar unlock: A callable which will unlock the lock.
 
96
    """
 
97
 
 
98
    def __init__(self, unlock):
 
99
        self.unlock = unlock
 
100
 
 
101
    def __repr__(self):
 
102
        return "LogicalLockResult(%s)" % (self.unlock)
 
103
 
 
104
 
 
105
 
 
106
def cant_unlock_not_held(locked_object):
 
107
    """An attempt to unlock failed because the object was not locked.
 
108
 
 
109
    This provides a policy point from which we can generate either a warning 
 
110
    or an exception.
 
111
    """
 
112
    # This is typically masking some other error and called from a finally
 
113
    # block, so it's useful to have the option not to generate a new error
 
114
    # here.  You can use -Werror to make it fatal.  It should possibly also
 
115
    # raise LockNotHeld.
 
116
    if 'unlock' in debug.debug_flags:
 
117
        warnings.warn("%r is already unlocked" % (locked_object,),
 
118
            stacklevel=3)
 
119
    else:
 
120
        raise errors.LockNotHeld(locked_object)
 
121
 
 
122
 
 
123
try:
 
124
    import fcntl
 
125
    have_fcntl = True
 
126
except ImportError:
 
127
    have_fcntl = False
 
128
 
 
129
have_pywin32 = False
 
130
have_ctypes_win32 = False
 
131
if sys.platform == 'win32':
 
132
    import msvcrt
 
133
    try:
 
134
        import win32file, pywintypes, winerror
 
135
        have_pywin32 = True
 
136
    except ImportError:
 
137
        pass
 
138
 
 
139
    try:
 
140
        import ctypes
 
141
        have_ctypes_win32 = True
 
142
    except ImportError:
 
143
        pass
 
144
 
 
145
 
 
146
class _OSLock(object):
 
147
 
 
148
    def __init__(self):
 
149
        self.f = None
 
150
        self.filename = None
 
151
 
 
152
    def _open(self, filename, filemode):
 
153
        self.filename = osutils.realpath(filename)
 
154
        try:
 
155
            self.f = open(self.filename, filemode)
 
156
            return self.f
 
157
        except IOError, e:
 
158
            if e.errno in (errno.EACCES, errno.EPERM):
 
159
                raise errors.LockFailed(self.filename, str(e))
 
160
            if e.errno != errno.ENOENT:
 
161
                raise
 
162
 
 
163
            # maybe this is an old branch (before may 2005)
 
164
            trace.mutter("trying to create missing lock %r", self.filename)
 
165
 
 
166
            self.f = open(self.filename, 'wb+')
 
167
            return self.f
 
168
 
 
169
    def _clear_f(self):
 
170
        """Clear the self.f attribute cleanly."""
 
171
        if self.f:
 
172
            self.f.close()
 
173
            self.f = None
 
174
 
 
175
    def unlock(self):
 
176
        raise NotImplementedError()
 
177
 
 
178
 
 
179
_lock_classes = []
 
180
 
 
181
 
 
182
if have_fcntl:
 
183
 
 
184
    class _fcntl_FileLock(_OSLock):
 
185
 
 
186
        def _unlock(self):
 
187
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
188
            self._clear_f()
 
189
 
 
190
 
 
191
    class _fcntl_WriteLock(_fcntl_FileLock):
 
192
 
 
193
        _open_locks = set()
 
194
 
 
195
        def __init__(self, filename):
 
196
            super(_fcntl_WriteLock, self).__init__()
 
197
            # Check we can grab a lock before we actually open the file.
 
198
            self.filename = osutils.realpath(filename)
 
199
            if self.filename in _fcntl_WriteLock._open_locks:
 
200
                self._clear_f()
 
201
                raise errors.LockContention(self.filename)
 
202
            if self.filename in _fcntl_ReadLock._open_locks:
 
203
                if 'strict_locks' in debug.debug_flags:
 
204
                    self._clear_f()
 
205
                    raise errors.LockContention(self.filename)
 
206
                else:
 
207
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
 
208
                                 % (self.filename,))
 
209
 
 
210
            self._open(self.filename, 'rb+')
 
211
            # reserve a slot for this lock - even if the lockf call fails,
 
212
            # at this point unlock() will be called, because self.f is set.
 
213
            # TODO: make this fully threadsafe, if we decide we care.
 
214
            _fcntl_WriteLock._open_locks.add(self.filename)
 
215
            try:
 
216
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
217
                # lock right away.
 
218
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
219
            except IOError, e:
 
220
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
221
                    # We couldn't grab the lock
 
222
                    self.unlock()
 
223
                # we should be more precise about whats a locking
 
224
                # error and whats a random-other error
 
225
                raise errors.LockContention(self.filename, e)
 
226
 
 
227
        def unlock(self):
 
228
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
229
            self._unlock()
 
230
 
 
231
 
 
232
    class _fcntl_ReadLock(_fcntl_FileLock):
 
233
 
 
234
        _open_locks = {}
 
235
 
 
236
        def __init__(self, filename):
 
237
            super(_fcntl_ReadLock, self).__init__()
 
238
            self.filename = osutils.realpath(filename)
 
239
            if self.filename in _fcntl_WriteLock._open_locks:
 
240
                if 'strict_locks' in debug.debug_flags:
 
241
                    # We raise before calling _open so we don't need to
 
242
                    # _clear_f
 
243
                    raise errors.LockContention(self.filename)
 
244
                else:
 
245
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
 
246
                                 % (self.filename,))
 
247
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
 
248
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
249
            self._open(filename, 'rb')
 
250
            try:
 
251
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
252
                # lock right away.
 
253
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
254
            except IOError, e:
 
255
                # we should be more precise about whats a locking
 
256
                # error and whats a random-other error
 
257
                raise errors.LockContention(self.filename, e)
 
258
 
 
259
        def unlock(self):
 
260
            count = _fcntl_ReadLock._open_locks[self.filename]
 
261
            if count == 1:
 
262
                del _fcntl_ReadLock._open_locks[self.filename]
 
263
            else:
 
264
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
 
265
            self._unlock()
 
266
 
 
267
        def temporary_write_lock(self):
 
268
            """Try to grab a write lock on the file.
 
269
 
 
270
            On platforms that support it, this will upgrade to a write lock
 
271
            without unlocking the file.
 
272
            Otherwise, this will release the read lock, and try to acquire a
 
273
            write lock.
 
274
 
 
275
            :return: A token which can be used to switch back to a read lock.
 
276
            """
 
277
            if self.filename in _fcntl_WriteLock._open_locks:
 
278
                raise AssertionError('file already locked: %r'
 
279
                    % (self.filename,))
 
280
            try:
 
281
                wlock = _fcntl_TemporaryWriteLock(self)
 
282
            except errors.LockError:
 
283
                # We didn't unlock, so we can just return 'self'
 
284
                return False, self
 
285
            return True, wlock
 
286
 
 
287
 
 
288
    class _fcntl_TemporaryWriteLock(_OSLock):
 
289
        """A token used when grabbing a temporary_write_lock.
 
290
 
 
291
        Call restore_read_lock() when you are done with the write lock.
 
292
        """
 
293
 
 
294
        def __init__(self, read_lock):
 
295
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
296
            self._read_lock = read_lock
 
297
            self.filename = read_lock.filename
 
298
 
 
299
            count = _fcntl_ReadLock._open_locks[self.filename]
 
300
            if count > 1:
 
301
                # Something else also has a read-lock, so we cannot grab a
 
302
                # write lock.
 
303
                raise errors.LockContention(self.filename)
 
304
 
 
305
            if self.filename in _fcntl_WriteLock._open_locks:
 
306
                raise AssertionError('file already locked: %r'
 
307
                    % (self.filename,))
 
308
 
 
309
            # See if we can open the file for writing. Another process might
 
310
            # have a read lock. We don't use self._open() because we don't want
 
311
            # to create the file if it exists. That would have already been
 
312
            # done by _fcntl_ReadLock
 
313
            try:
 
314
                new_f = open(self.filename, 'rb+')
 
315
            except IOError, e:
 
316
                if e.errno in (errno.EACCES, errno.EPERM):
 
317
                    raise errors.LockFailed(self.filename, str(e))
 
318
                raise
 
319
            try:
 
320
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
321
                # lock right away.
 
322
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
323
            except IOError, e:
 
324
                # TODO: Raise a more specific error based on the type of error
 
325
                raise errors.LockContention(self.filename, e)
 
326
            _fcntl_WriteLock._open_locks.add(self.filename)
 
327
 
 
328
            self.f = new_f
 
329
 
 
330
        def restore_read_lock(self):
 
331
            """Restore the original ReadLock."""
 
332
            # For fcntl, since we never released the read lock, just release the
 
333
            # write lock, and return the original lock.
 
334
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
335
            self._clear_f()
 
336
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
337
            # Avoid reference cycles
 
338
            read_lock = self._read_lock
 
339
            self._read_lock = None
 
340
            return read_lock
 
341
 
 
342
 
 
343
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
 
344
 
 
345
 
 
346
if have_pywin32 and sys.platform == 'win32':
 
347
    if os.path.supports_unicode_filenames:
 
348
        # for Windows NT/2K/XP/etc
 
349
        win32file_CreateFile = win32file.CreateFileW
 
350
    else:
 
351
        # for Windows 98
 
352
        win32file_CreateFile = win32file.CreateFile
 
353
 
 
354
    class _w32c_FileLock(_OSLock):
 
355
 
 
356
        def _open(self, filename, access, share, cflags, pymode):
 
357
            self.filename = osutils.realpath(filename)
 
358
            try:
 
359
                self._handle = win32file_CreateFile(filename, access, share,
 
360
                    None, win32file.OPEN_ALWAYS,
 
361
                    win32file.FILE_ATTRIBUTE_NORMAL, None)
 
362
            except pywintypes.error, e:
 
363
                if e.args[0] == winerror.ERROR_ACCESS_DENIED:
 
364
                    raise errors.LockFailed(filename, e)
 
365
                if e.args[0] == winerror.ERROR_SHARING_VIOLATION:
 
366
                    raise errors.LockContention(filename, e)
 
367
                raise
 
368
            fd = win32file._open_osfhandle(self._handle, cflags)
 
369
            self.f = os.fdopen(fd, pymode)
 
370
            return self.f
 
371
 
 
372
        def unlock(self):
 
373
            self._clear_f()
 
374
            self._handle = None
 
375
 
 
376
 
 
377
    class _w32c_ReadLock(_w32c_FileLock):
 
378
        def __init__(self, filename):
 
379
            super(_w32c_ReadLock, self).__init__()
 
380
            self._open(filename, win32file.GENERIC_READ,
 
381
                win32file.FILE_SHARE_READ, os.O_RDONLY, "rb")
 
382
 
 
383
        def temporary_write_lock(self):
 
384
            """Try to grab a write lock on the file.
 
385
 
 
386
            On platforms that support it, this will upgrade to a write lock
 
387
            without unlocking the file.
 
388
            Otherwise, this will release the read lock, and try to acquire a
 
389
            write lock.
 
390
 
 
391
            :return: A token which can be used to switch back to a read lock.
 
392
            """
 
393
            # I can't find a way to upgrade a read lock to a write lock without
 
394
            # unlocking first. So here, we do just that.
 
395
            self.unlock()
 
396
            try:
 
397
                wlock = _w32c_WriteLock(self.filename)
 
398
            except errors.LockError:
 
399
                return False, _w32c_ReadLock(self.filename)
 
400
            return True, wlock
 
401
 
 
402
 
 
403
    class _w32c_WriteLock(_w32c_FileLock):
 
404
        def __init__(self, filename):
 
405
            super(_w32c_WriteLock, self).__init__()
 
406
            self._open(filename,
 
407
                win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0,
 
408
                os.O_RDWR, "rb+")
 
409
 
 
410
        def restore_read_lock(self):
 
411
            """Restore the original ReadLock."""
 
412
            # For win32 we had to completely let go of the original lock, so we
 
413
            # just unlock and create a new read lock.
 
414
            self.unlock()
 
415
            return _w32c_ReadLock(self.filename)
 
416
 
 
417
 
 
418
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
419
 
 
420
 
 
421
if have_ctypes_win32:
 
422
    from ctypes.wintypes import DWORD, LPCSTR, LPCWSTR
 
423
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p # used as NULL no need to declare
 
424
    HANDLE = ctypes.c_int # rather than unsigned as in ctypes.wintypes
 
425
    if os.path.supports_unicode_filenames:
 
426
        _function_name = "CreateFileW"
 
427
        LPTSTR = LPCWSTR
 
428
    else:
 
429
        _function_name = "CreateFileA"
 
430
        class LPTSTR(LPCSTR):
 
431
            def __new__(cls, obj):
 
432
                return LPCSTR.__new__(cls, obj.encode("mbcs"))
 
433
 
 
434
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
 
435
    _CreateFile = ctypes.WINFUNCTYPE(
 
436
            HANDLE,                # return value
 
437
            LPTSTR,                # lpFileName
 
438
            DWORD,                 # dwDesiredAccess
 
439
            DWORD,                 # dwShareMode
 
440
            LPSECURITY_ATTRIBUTES, # lpSecurityAttributes
 
441
            DWORD,                 # dwCreationDisposition
 
442
            DWORD,                 # dwFlagsAndAttributes
 
443
            HANDLE                 # hTemplateFile
 
444
        )((_function_name, ctypes.windll.kernel32))
 
445
 
 
446
    INVALID_HANDLE_VALUE = -1
 
447
 
 
448
    GENERIC_READ = 0x80000000
 
449
    GENERIC_WRITE = 0x40000000
 
450
    FILE_SHARE_READ = 1
 
451
    OPEN_ALWAYS = 4
 
452
    FILE_ATTRIBUTE_NORMAL = 128
 
453
 
 
454
    ERROR_ACCESS_DENIED = 5
 
455
    ERROR_SHARING_VIOLATION = 32
 
456
 
 
457
    class _ctypes_FileLock(_OSLock):
 
458
 
 
459
        def _open(self, filename, access, share, cflags, pymode):
 
460
            self.filename = osutils.realpath(filename)
 
461
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
 
462
                FILE_ATTRIBUTE_NORMAL, 0)
 
463
            if handle in (INVALID_HANDLE_VALUE, 0):
 
464
                e = ctypes.WinError()
 
465
                if e.args[0] == ERROR_ACCESS_DENIED:
 
466
                    raise errors.LockFailed(filename, e)
 
467
                if e.args[0] == ERROR_SHARING_VIOLATION:
 
468
                    raise errors.LockContention(filename, e)
 
469
                raise e
 
470
            fd = msvcrt.open_osfhandle(handle, cflags)
 
471
            self.f = os.fdopen(fd, pymode)
 
472
            return self.f
 
473
 
 
474
        def unlock(self):
 
475
            self._clear_f()
 
476
 
 
477
 
 
478
    class _ctypes_ReadLock(_ctypes_FileLock):
 
479
        def __init__(self, filename):
 
480
            super(_ctypes_ReadLock, self).__init__()
 
481
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
 
482
                "rb")
 
483
 
 
484
        def temporary_write_lock(self):
 
485
            """Try to grab a write lock on the file.
 
486
 
 
487
            On platforms that support it, this will upgrade to a write lock
 
488
            without unlocking the file.
 
489
            Otherwise, this will release the read lock, and try to acquire a
 
490
            write lock.
 
491
 
 
492
            :return: A token which can be used to switch back to a read lock.
 
493
            """
 
494
            # I can't find a way to upgrade a read lock to a write lock without
 
495
            # unlocking first. So here, we do just that.
 
496
            self.unlock()
 
497
            try:
 
498
                wlock = _ctypes_WriteLock(self.filename)
 
499
            except errors.LockError:
 
500
                return False, _ctypes_ReadLock(self.filename)
 
501
            return True, wlock
 
502
 
 
503
    class _ctypes_WriteLock(_ctypes_FileLock):
 
504
        def __init__(self, filename):
 
505
            super(_ctypes_WriteLock, self).__init__()
 
506
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
 
507
                "rb+")
 
508
 
 
509
        def restore_read_lock(self):
 
510
            """Restore the original ReadLock."""
 
511
            # For win32 we had to completely let go of the original lock, so we
 
512
            # just unlock and create a new read lock.
 
513
            self.unlock()
 
514
            return _ctypes_ReadLock(self.filename)
 
515
 
 
516
 
 
517
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
 
518
 
 
519
 
 
520
if len(_lock_classes) == 0:
 
521
    raise NotImplementedError(
 
522
        "We must have one of fcntl, pywin32, or ctypes available"
 
523
        " to support OS locking."
 
524
        )
 
525
 
 
526
 
 
527
# We default to using the first available lock class.
 
528
_lock_type, WriteLock, ReadLock = _lock_classes[0]
 
529
 
 
530
 
 
531
class _RelockDebugMixin(object):
 
532
    """Mixin support for -Drelock flag.
 
533
 
 
534
    Add this as a base class then call self._note_lock with 'r' or 'w' when
 
535
    acquiring a read- or write-lock.  If this object was previously locked (and
 
536
    locked the same way), and -Drelock is set, then this will trace.note a
 
537
    message about it.
 
538
    """
 
539
 
 
540
    _prev_lock = None
 
541
 
 
542
    def _note_lock(self, lock_type):
 
543
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
 
544
            if lock_type == 'r':
 
545
                type_name = 'read'
 
546
            else:
 
547
                type_name = 'write'
 
548
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
 
549
        self._prev_lock = lock_type
 
550