~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Martin Pool
  • Date: 2005-04-28 07:24:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050428072453-7b99afa993a1e549
todo

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