/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
16
17
 
17
18
"""Locking using OS file locks or file existence.
18
19
 
33
34
unlock() method.
34
35
"""
35
36
 
36
 
import contextlib
37
37
import errno
38
 
import os
39
38
import sys
40
 
import warnings
41
39
 
42
 
from . import (
43
 
    debug,
 
40
from bzrlib import (
44
41
    errors,
45
42
    osutils,
46
43
    trace,
47
44
    )
48
 
from .hooks import Hooks
49
 
from .i18n import gettext
50
 
 
51
 
 
52
 
class LockHooks(Hooks):
53
 
 
54
 
    def __init__(self):
55
 
        Hooks.__init__(self, "breezy.lock", "Lock.hooks")
56
 
        self.add_hook(
57
 
            'lock_acquired',
58
 
            "Called with a breezy.lock.LockResult when a physical lock is "
59
 
            "acquired.", (1, 8))
60
 
        self.add_hook(
61
 
            'lock_released',
62
 
            "Called with a breezy.lock.LockResult when a physical lock is "
63
 
            "released.", (1, 8))
64
 
        self.add_hook(
65
 
            'lock_broken',
66
 
            "Called with a breezy.lock.LockResult when a physical lock is "
67
 
            "broken.", (1, 15))
68
 
 
69
 
 
70
 
class Lock(object):
71
 
    """Base class for locks.
72
 
 
73
 
    :cvar hooks: Hook dictionary for operations on locks.
74
 
    """
75
 
 
76
 
    hooks = LockHooks()
77
 
 
78
 
 
79
 
class LockResult(object):
80
 
    """Result of an operation on a lock; passed to a hook"""
81
 
 
82
 
    def __init__(self, lock_url, details=None):
83
 
        """Create a lock result for lock with optional details about the lock."""
84
 
        self.lock_url = lock_url
85
 
        self.details = details
86
 
 
87
 
    def __eq__(self, other):
88
 
        return self.lock_url == other.lock_url and self.details == other.details
89
 
 
90
 
    def __repr__(self):
91
 
        return '%s(%s, %s)' % (self.__class__.__name__,
92
 
                               self.lock_url, self.details)
93
 
 
94
 
 
95
 
class LogicalLockResult(object):
96
 
    """The result of a lock_read/lock_write/lock_tree_write call on lockables.
97
 
 
98
 
    :ivar unlock: A callable which will unlock the lock.
99
 
    """
100
 
 
101
 
    def __init__(self, unlock, token=None):
102
 
        self.unlock = unlock
103
 
        self.token = token
104
 
 
105
 
    def __repr__(self):
106
 
        return "LogicalLockResult(%s)" % (self.unlock)
107
 
 
108
 
    def __enter__(self):
109
 
        return self
110
 
 
111
 
    def __exit__(self, exc_type, exc_val, exc_tb):
112
 
        # If there was an error raised, prefer the original one
113
 
        try:
114
 
            self.unlock()
115
 
        except BaseException:
116
 
            if exc_type is None:
117
 
                raise
118
 
        return False
119
 
 
120
 
 
121
 
def cant_unlock_not_held(locked_object):
122
 
    """An attempt to unlock failed because the object was not locked.
123
 
 
124
 
    This provides a policy point from which we can generate either a warning or
125
 
    an exception.
126
 
    """
127
 
    # This is typically masking some other error and called from a finally
128
 
    # block, so it's useful to have the option not to generate a new error
129
 
    # here.  You can use -Werror to make it fatal.  It should possibly also
130
 
    # raise LockNotHeld.
131
 
    if 'unlock' in debug.debug_flags:
132
 
        warnings.warn("%r is already unlocked" % (locked_object,),
133
 
                      stacklevel=3)
134
 
    else:
135
 
        raise errors.LockNotHeld(locked_object)
136
 
 
137
 
 
138
 
try:
139
 
    import fcntl
140
 
    have_fcntl = True
141
 
except ImportError:
142
 
    have_fcntl = False
143
 
 
144
 
have_ctypes_win32 = False
145
 
if sys.platform == 'win32':
146
 
    import msvcrt
147
 
    try:
148
 
        import ctypes
149
 
        have_ctypes_win32 = True
150
 
    except ImportError:
151
 
        pass
152
 
 
153
 
 
154
 
class _OSLock(object):
 
45
 
 
46
 
 
47
class _base_Lock(object):
155
48
 
156
49
    def __init__(self):
157
50
        self.f = None
162
55
        try:
163
56
            self.f = open(self.filename, filemode)
164
57
            return self.f
165
 
        except IOError as e:
 
58
        except IOError, e:
166
59
            if e.errno in (errno.EACCES, errno.EPERM):
167
 
                raise errors.LockFailed(self.filename, str(e))
 
60
                raise errors.ReadOnlyLockError(self.filename, str(e))
168
61
            if e.errno != errno.ENOENT:
169
62
                raise
170
63
 
180
73
            self.f.close()
181
74
            self.f = None
182
75
 
 
76
    def __del__(self):
 
77
        if self.f:
 
78
            from warnings import warn
 
79
            warn("lock on %r not released" % self.f)
 
80
            self.unlock()
 
81
 
183
82
    def unlock(self):
184
83
        raise NotImplementedError()
185
84
 
186
85
 
 
86
try:
 
87
    import fcntl
 
88
    have_fcntl = True
 
89
except ImportError:
 
90
    have_fcntl = False
 
91
try:
 
92
    import win32con, win32file, pywintypes, winerror, msvcrt
 
93
    have_pywin32 = True
 
94
except ImportError:
 
95
    have_pywin32 = False
 
96
try:
 
97
    import ctypes, msvcrt
 
98
    have_ctypes = True
 
99
except ImportError:
 
100
    have_ctypes = False
 
101
 
 
102
 
187
103
_lock_classes = []
188
104
 
189
105
 
190
106
if have_fcntl:
191
 
 
192
 
    class _fcntl_FileLock(_OSLock):
 
107
    LOCK_SH = fcntl.LOCK_SH
 
108
    LOCK_NB = fcntl.LOCK_NB
 
109
    lock_EX = fcntl.LOCK_EX
 
110
 
 
111
 
 
112
    class _fcntl_FileLock(_base_Lock):
193
113
 
194
114
        def _unlock(self):
195
115
            fcntl.lockf(self.f, fcntl.LOCK_UN)
196
116
            self._clear_f()
197
117
 
 
118
 
198
119
    class _fcntl_WriteLock(_fcntl_FileLock):
199
120
 
200
121
        _open_locks = set()
206
127
            if self.filename in _fcntl_WriteLock._open_locks:
207
128
                self._clear_f()
208
129
                raise errors.LockContention(self.filename)
209
 
            if self.filename in _fcntl_ReadLock._open_locks:
210
 
                if 'strict_locks' in debug.debug_flags:
211
 
                    self._clear_f()
212
 
                    raise errors.LockContention(self.filename)
213
 
                else:
214
 
                    trace.mutter('Write lock taken w/ an open read lock on: %s'
215
 
                                 % (self.filename,))
216
130
 
217
131
            self._open(self.filename, 'rb+')
218
132
            # reserve a slot for this lock - even if the lockf call fails,
219
 
            # at this point unlock() will be called, because self.f is set.
 
133
            # at thisi point unlock() will be called, because self.f is set.
220
134
            # TODO: make this fully threadsafe, if we decide we care.
221
135
            _fcntl_WriteLock._open_locks.add(self.filename)
222
136
            try:
223
137
                # LOCK_NB will cause IOError to be raised if we can't grab a
224
138
                # lock right away.
225
139
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
226
 
            except IOError as e:
 
140
            except IOError, e:
227
141
                if e.errno in (errno.EAGAIN, errno.EACCES):
228
142
                    # We couldn't grab the lock
229
143
                    self.unlock()
230
144
                # we should be more precise about whats a locking
231
145
                # error and whats a random-other error
232
 
                raise errors.LockContention(self.filename, e)
 
146
                raise errors.LockContention(e)
233
147
 
234
148
        def unlock(self):
235
149
            _fcntl_WriteLock._open_locks.remove(self.filename)
236
150
            self._unlock()
237
151
 
 
152
 
238
153
    class _fcntl_ReadLock(_fcntl_FileLock):
239
154
 
240
155
        _open_locks = {}
242
157
        def __init__(self, filename):
243
158
            super(_fcntl_ReadLock, self).__init__()
244
159
            self.filename = osutils.realpath(filename)
245
 
            if self.filename in _fcntl_WriteLock._open_locks:
246
 
                if 'strict_locks' in debug.debug_flags:
247
 
                    # We raise before calling _open so we don't need to
248
 
                    # _clear_f
249
 
                    raise errors.LockContention(self.filename)
250
 
                else:
251
 
                    trace.mutter('Read lock taken w/ an open write lock on: %s'
252
 
                                 % (self.filename,))
253
160
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
254
161
            _fcntl_ReadLock._open_locks[self.filename] += 1
255
162
            self._open(filename, 'rb')
257
164
                # LOCK_NB will cause IOError to be raised if we can't grab a
258
165
                # lock right away.
259
166
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
260
 
            except IOError as e:
 
167
            except IOError, e:
261
168
                # we should be more precise about whats a locking
262
169
                # error and whats a random-other error
263
 
                raise errors.LockContention(self.filename, e)
 
170
                raise errors.LockContention(e)
264
171
 
265
172
        def unlock(self):
266
173
            count = _fcntl_ReadLock._open_locks[self.filename]
280
187
 
281
188
            :return: A token which can be used to switch back to a read lock.
282
189
            """
283
 
            if self.filename in _fcntl_WriteLock._open_locks:
284
 
                raise AssertionError('file already locked: %r'
285
 
                                     % (self.filename,))
 
190
            assert self.filename not in _fcntl_WriteLock._open_locks
286
191
            try:
287
192
                wlock = _fcntl_TemporaryWriteLock(self)
288
193
            except errors.LockError:
290
195
                return False, self
291
196
            return True, wlock
292
197
 
293
 
    class _fcntl_TemporaryWriteLock(_OSLock):
 
198
 
 
199
    class _fcntl_TemporaryWriteLock(_base_Lock):
294
200
        """A token used when grabbing a temporary_write_lock.
295
201
 
296
202
        Call restore_read_lock() when you are done with the write lock.
307
213
                # write lock.
308
214
                raise errors.LockContention(self.filename)
309
215
 
310
 
            if self.filename in _fcntl_WriteLock._open_locks:
311
 
                raise AssertionError('file already locked: %r'
312
 
                                     % (self.filename,))
 
216
            assert self.filename not in _fcntl_WriteLock._open_locks
313
217
 
314
218
            # See if we can open the file for writing. Another process might
315
219
            # have a read lock. We don't use self._open() because we don't want
317
221
            # done by _fcntl_ReadLock
318
222
            try:
319
223
                new_f = open(self.filename, 'rb+')
320
 
            except IOError as e:
 
224
            except IOError, e:
321
225
                if e.errno in (errno.EACCES, errno.EPERM):
322
 
                    raise errors.LockFailed(self.filename, str(e))
 
226
                    raise errors.ReadOnlyLockError(self.filename, str(e))
323
227
                raise
324
228
            try:
325
229
                # LOCK_NB will cause IOError to be raised if we can't grab a
326
230
                # lock right away.
327
231
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
328
 
            except IOError as e:
 
232
            except IOError, e:
329
233
                # TODO: Raise a more specific error based on the type of error
330
 
                raise errors.LockContention(self.filename, e)
 
234
                raise errors.LockContention(e)
331
235
            _fcntl_WriteLock._open_locks.add(self.filename)
332
236
 
333
237
            self.f = new_f
334
238
 
335
239
        def restore_read_lock(self):
336
240
            """Restore the original ReadLock."""
337
 
            # For fcntl, since we never released the read lock, just release
338
 
            # the write lock, and return the original lock.
 
241
            # For fcntl, since we never released the read lock, just release the
 
242
            # write lock, and return the original lock.
339
243
            fcntl.lockf(self.f, fcntl.LOCK_UN)
340
244
            self._clear_f()
341
245
            _fcntl_WriteLock._open_locks.remove(self.filename)
344
248
            self._read_lock = None
345
249
            return read_lock
346
250
 
 
251
 
347
252
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
348
253
 
349
254
 
350
 
if have_ctypes_win32:
351
 
    from ctypes.wintypes import DWORD, LPWSTR
352
 
    LPSECURITY_ATTRIBUTES = ctypes.c_void_p  # used as NULL no need to declare
353
 
    HANDLE = ctypes.c_int  # rather than unsigned as in ctypes.wintypes
354
 
    _function_name = "CreateFileW"
355
 
 
356
 
    # CreateFile <http://msdn.microsoft.com/en-us/library/aa363858.aspx>
357
 
    _CreateFile = ctypes.WINFUNCTYPE(
358
 
        HANDLE,                # return value
359
 
        LPWSTR,                # lpFileName
360
 
        DWORD,                 # dwDesiredAccess
361
 
        DWORD,                 # dwShareMode
362
 
        LPSECURITY_ATTRIBUTES,  # lpSecurityAttributes
363
 
        DWORD,                 # dwCreationDisposition
364
 
        DWORD,                 # dwFlagsAndAttributes
365
 
        HANDLE                 # hTemplateFile
366
 
        )((_function_name, ctypes.windll.kernel32))
367
 
 
368
 
    INVALID_HANDLE_VALUE = -1
369
 
 
370
 
    GENERIC_READ = 0x80000000
371
 
    GENERIC_WRITE = 0x40000000
372
 
    FILE_SHARE_READ = 1
373
 
    OPEN_ALWAYS = 4
374
 
    FILE_ATTRIBUTE_NORMAL = 128
375
 
 
376
 
    ERROR_ACCESS_DENIED = 5
377
 
    ERROR_SHARING_VIOLATION = 32
378
 
 
379
 
    class _ctypes_FileLock(_OSLock):
380
 
 
381
 
        def _open(self, filename, access, share, cflags, pymode):
382
 
            self.filename = osutils.realpath(filename)
383
 
            handle = _CreateFile(filename, access, share, None, OPEN_ALWAYS,
384
 
                                 FILE_ATTRIBUTE_NORMAL, 0)
385
 
            if handle in (INVALID_HANDLE_VALUE, 0):
386
 
                e = ctypes.WinError()
387
 
                if e.args[0] == ERROR_ACCESS_DENIED:
388
 
                    raise errors.LockFailed(filename, e)
389
 
                if e.args[0] == ERROR_SHARING_VIOLATION:
390
 
                    raise errors.LockContention(filename, e)
391
 
                raise e
392
 
            fd = msvcrt.open_osfhandle(handle, cflags)
393
 
            self.f = os.fdopen(fd, pymode)
394
 
            return self.f
395
 
 
396
 
        def unlock(self):
 
255
if have_pywin32 and sys.platform == 'win32':
 
256
    LOCK_SH = 0 # the default
 
257
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
258
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
259
 
 
260
 
 
261
    class _w32c_FileLock(_base_Lock):
 
262
 
 
263
        def _lock(self, filename, openmode, lockmode):
 
264
            self._open(filename, openmode)
 
265
 
 
266
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
267
            overlapped = pywintypes.OVERLAPPED()
 
268
            try:
 
269
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
 
270
                                     overlapped)
 
271
            except pywintypes.error, e:
 
272
                self._clear_f()
 
273
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
 
274
                    raise errors.LockContention(filename)
 
275
                ## import pdb; pdb.set_trace()
 
276
                raise
 
277
            except Exception, e:
 
278
                self._clear_f()
 
279
                raise errors.LockContention(e)
 
280
 
 
281
        def unlock(self):
 
282
            overlapped = pywintypes.OVERLAPPED()
 
283
            try:
 
284
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
285
                self._clear_f()
 
286
            except Exception, e:
 
287
                raise errors.LockContention(e)
 
288
 
 
289
 
 
290
    class _w32c_ReadLock(_w32c_FileLock):
 
291
        def __init__(self, filename):
 
292
            super(_w32c_ReadLock, self).__init__()
 
293
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
 
294
 
 
295
        def temporary_write_lock(self):
 
296
            """Try to grab a write lock on the file.
 
297
 
 
298
            On platforms that support it, this will upgrade to a write lock
 
299
            without unlocking the file.
 
300
            Otherwise, this will release the read lock, and try to acquire a
 
301
            write lock.
 
302
 
 
303
            :return: A token which can be used to switch back to a read lock.
 
304
            """
 
305
            # I can't find a way to upgrade a read lock to a write lock without
 
306
            # unlocking first. So here, we do just that.
 
307
            self.unlock()
 
308
            try:
 
309
                wlock = _w32c_WriteLock(self.filename)
 
310
            except errors.LockError:
 
311
                return False, _w32c_ReadLock(self.filename)
 
312
            return True, wlock
 
313
 
 
314
 
 
315
    class _w32c_WriteLock(_w32c_FileLock):
 
316
        def __init__(self, filename):
 
317
            super(_w32c_WriteLock, self).__init__()
 
318
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
 
319
 
 
320
        def restore_read_lock(self):
 
321
            """Restore the original ReadLock."""
 
322
            # For win32 we had to completely let go of the original lock, so we
 
323
            # just unlock and create a new read lock.
 
324
            self.unlock()
 
325
            return _w32c_ReadLock(self.filename)
 
326
 
 
327
 
 
328
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
 
329
 
 
330
 
 
331
if have_ctypes and sys.platform == 'win32':
 
332
    # These constants were copied from the win32con.py module.
 
333
    LOCKFILE_FAIL_IMMEDIATELY = 1
 
334
    LOCKFILE_EXCLUSIVE_LOCK = 2
 
335
    # Constant taken from winerror.py module
 
336
    ERROR_LOCK_VIOLATION = 33
 
337
 
 
338
    LOCK_SH = 0
 
339
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
 
340
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
 
341
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
 
342
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
 
343
    _GetLastError = ctypes.windll.kernel32.GetLastError
 
344
 
 
345
    ### Define the OVERLAPPED structure.
 
346
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
 
347
    # typedef struct _OVERLAPPED {
 
348
    #   ULONG_PTR Internal;
 
349
    #   ULONG_PTR InternalHigh;
 
350
    #   union {
 
351
    #     struct {
 
352
    #       DWORD Offset;
 
353
    #       DWORD OffsetHigh;
 
354
    #     };
 
355
    #     PVOID Pointer;
 
356
    #   };
 
357
    #   HANDLE hEvent;
 
358
    # } OVERLAPPED,
 
359
 
 
360
    class _inner_struct(ctypes.Structure):
 
361
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
 
362
                    ('OffsetHigh', ctypes.c_uint), # DWORD
 
363
                   ]
 
364
 
 
365
    class _inner_union(ctypes.Union):
 
366
        _fields_  = [('anon_struct', _inner_struct), # struct
 
367
                     ('Pointer', ctypes.c_void_p), # PVOID
 
368
                    ]
 
369
 
 
370
    class OVERLAPPED(ctypes.Structure):
 
371
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
 
372
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
 
373
                    ('_inner_union', _inner_union),
 
374
                    ('hEvent', ctypes.c_void_p), # HANDLE
 
375
                   ]
 
376
 
 
377
    class _ctypes_FileLock(_base_Lock):
 
378
 
 
379
        def _lock(self, filename, openmode, lockmode):
 
380
            self._open(filename, openmode)
 
381
 
 
382
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
 
383
            overlapped = OVERLAPPED()
 
384
            result = _LockFileEx(self.hfile, # HANDLE hFile
 
385
                                 lockmode,   # DWORD dwFlags
 
386
                                 0,          # DWORD dwReserved
 
387
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
388
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
 
389
                                 ctypes.byref(overlapped), # lpOverlapped
 
390
                                )
 
391
            if result == 0:
 
392
                self._clear_f()
 
393
                last_err = _GetLastError()
 
394
                if last_err in (ERROR_LOCK_VIOLATION,):
 
395
                    raise errors.LockContention(filename)
 
396
                raise errors.LockContention('Unknown locking error: %s'
 
397
                                            % (last_err,))
 
398
 
 
399
        def unlock(self):
 
400
            overlapped = OVERLAPPED()
 
401
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
 
402
                                   0,          # DWORD dwReserved
 
403
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
 
404
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
 
405
                                   ctypes.byref(overlapped), # lpOverlapped
 
406
                                  )
397
407
            self._clear_f()
 
408
            if result == 0:
 
409
                self._clear_f()
 
410
                last_err = _GetLastError()
 
411
                raise errors.LockContention('Unknown unlocking error: %s'
 
412
                                            % (last_err,))
 
413
 
398
414
 
399
415
    class _ctypes_ReadLock(_ctypes_FileLock):
400
416
        def __init__(self, filename):
401
417
            super(_ctypes_ReadLock, self).__init__()
402
 
            self._open(filename, GENERIC_READ, FILE_SHARE_READ, os.O_RDONLY,
403
 
                       "rb")
 
418
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
404
419
 
405
420
        def temporary_write_lock(self):
406
421
            """Try to grab a write lock on the file.
424
439
    class _ctypes_WriteLock(_ctypes_FileLock):
425
440
        def __init__(self, filename):
426
441
            super(_ctypes_WriteLock, self).__init__()
427
 
            self._open(filename, GENERIC_READ | GENERIC_WRITE, 0, os.O_RDWR,
428
 
                       "rb+")
 
442
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
429
443
 
430
444
        def restore_read_lock(self):
431
445
            """Restore the original ReadLock."""
434
448
            self.unlock()
435
449
            return _ctypes_ReadLock(self.filename)
436
450
 
 
451
 
437
452
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
438
453
 
439
454
 
440
455
if len(_lock_classes) == 0:
441
456
    raise NotImplementedError(
442
 
        "We must have one of fcntl or ctypes available"
 
457
        "We must have one of fcntl, pywin32, or ctypes available"
443
458
        " to support OS locking."
444
459
        )
445
460
 
447
462
# We default to using the first available lock class.
448
463
_lock_type, WriteLock, ReadLock = _lock_classes[0]
449
464
 
450
 
 
451
 
class _RelockDebugMixin(object):
452
 
    """Mixin support for -Drelock flag.
453
 
 
454
 
    Add this as a base class then call self._note_lock with 'r' or 'w' when
455
 
    acquiring a read- or write-lock.  If this object was previously locked (and
456
 
    locked the same way), and -Drelock is set, then this will trace.note a
457
 
    message about it.
458
 
    """
459
 
 
460
 
    _prev_lock = None
461
 
 
462
 
    def _note_lock(self, lock_type):
463
 
        if 'relock' in debug.debug_flags and self._prev_lock == lock_type:
464
 
            if lock_type == 'r':
465
 
                type_name = 'read'
466
 
            else:
467
 
                type_name = 'write'
468
 
            trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
469
 
        self._prev_lock = lock_type
470
 
 
471
 
 
472
 
@contextlib.contextmanager
473
 
def write_locked(lockable):
474
 
    lockable.lock_write()
475
 
    try:
476
 
        yield lockable
477
 
    finally:
478
 
        lockable.unlock()