~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

Better readv coalescing, now with test, and progress during knit index reading.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
26
 
27
27
It is not specified whether these locks are reentrant (i.e. can be
28
28
taken repeatedly by a single process) or whether they exclude
29
 
different threads in a single process.  That reentrancy is provided by
 
29
different threads in a single process.  That reentrancy is provided by 
30
30
LockableFiles.
31
31
 
32
32
This defines two classes: ReadLock and WriteLock, which can be
35
35
"""
36
36
 
37
37
import errno
 
38
import os
38
39
import sys
39
40
 
40
 
from bzrlib import (
41
 
    errors,
42
 
    osutils,
43
 
    trace,
44
 
    )
45
 
from bzrlib.hooks import Hooks
46
 
 
47
 
 
48
 
class LockHooks(Hooks):
49
 
 
50
 
    def __init__(self):
51
 
        Hooks.__init__(self)
52
 
 
53
 
        # added in 1.8; called with a LockResult when a physical lock is
54
 
        # acquired
55
 
        self['lock_acquired'] = []
56
 
 
57
 
        # added in 1.8; called with a LockResult when a physical lock is
58
 
        # acquired
59
 
        self['lock_released'] = []
60
 
 
61
 
 
62
 
class Lock(object):
63
 
    """Base class for locks.
64
 
 
65
 
    :cvar hooks: Hook dictionary for operations on locks.
66
 
    """
67
 
 
68
 
    hooks = LockHooks()
69
 
 
70
 
 
71
 
class LockResult(object):
72
 
    """Result of an operation on a lock; passed to a hook"""
73
 
 
74
 
    def __init__(self, lock_url, details=None):
75
 
        """Create a lock result for lock with optional details about the lock."""
76
 
        self.lock_url = lock_url
77
 
        self.details = details
78
 
 
79
 
    def __eq__(self, other):
80
 
        return self.lock_url == other.lock_url and self.details == other.details
81
 
 
82
 
 
83
 
try:
84
 
    import fcntl
85
 
    have_fcntl = True
86
 
except ImportError:
87
 
    have_fcntl = False
88
 
 
89
 
have_pywin32 = False
90
 
have_ctypes_win32 = False
91
 
if sys.platform == 'win32':
92
 
    import msvcrt
93
 
    try:
94
 
        import win32con, win32file, pywintypes, winerror
95
 
        have_pywin32 = True
96
 
    except ImportError:
97
 
        pass
98
 
 
99
 
    try:
100
 
        import ctypes
101
 
        have_ctypes_win32 = True
102
 
    except ImportError:
103
 
        pass
104
 
 
105
 
 
106
 
class _OSLock(object):
107
 
 
108
 
    def __init__(self):
109
 
        self.f = None
110
 
        self.filename = None
111
 
 
 
41
from bzrlib.trace import mutter, note, warning
 
42
from bzrlib.errors import LockError
 
43
 
 
44
class _base_Lock(object):
112
45
    def _open(self, filename, filemode):
113
 
        self.filename = osutils.realpath(filename)
114
46
        try:
115
 
            self.f = open(self.filename, filemode)
 
47
            self.f = open(filename, filemode)
116
48
            return self.f
117
49
        except IOError, e:
118
 
            if e.errno in (errno.EACCES, errno.EPERM):
119
 
                raise errors.LockFailed(self.filename, str(e))
120
50
            if e.errno != errno.ENOENT:
121
51
                raise
122
52
 
123
53
            # maybe this is an old branch (before may 2005)
124
 
            trace.mutter("trying to create missing lock %r", self.filename)
125
 
 
126
 
            self.f = open(self.filename, 'wb+')
 
54
            mutter("trying to create missing branch lock %r", filename)
 
55
            
 
56
            self.f = open(filename, 'wb+')
127
57
            return self.f
128
58
 
129
 
    def _clear_f(self):
130
 
        """Clear the self.f attribute cleanly."""
131
 
        if self.f:
132
 
            self.f.close()
133
 
            self.f = None
134
 
 
135
59
    def __del__(self):
136
60
        if self.f:
137
61
            from warnings import warn
138
62
            warn("lock on %r not released" % self.f)
139
63
            self.unlock()
140
 
 
 
64
            
141
65
    def unlock(self):
142
66
        raise NotImplementedError()
143
67
 
144
 
 
145
 
_lock_classes = []
146
 
 
147
 
 
148
 
if have_fcntl:
149
 
    LOCK_SH = fcntl.LOCK_SH
150
 
    LOCK_NB = fcntl.LOCK_NB
151
 
    lock_EX = fcntl.LOCK_EX
152
 
 
153
 
 
154
 
    class _fcntl_FileLock(_OSLock):
155
 
 
156
 
        def _unlock(self):
 
68
        
 
69
 
 
70
 
 
71
 
 
72
 
 
73
############################################################
 
74
# msvcrt locks
 
75
 
 
76
 
 
77
try:
 
78
    import fcntl
 
79
 
 
80
    class _fcntl_FileLock(_base_Lock):
 
81
        f = None
 
82
 
 
83
        def unlock(self):
157
84
            fcntl.lockf(self.f, fcntl.LOCK_UN)
158
 
            self._clear_f()
159
 
 
 
85
            self.f.close()
 
86
            del self.f 
160
87
 
161
88
    class _fcntl_WriteLock(_fcntl_FileLock):
162
 
 
163
 
        _open_locks = set()
164
 
 
165
89
        def __init__(self, filename):
166
 
            super(_fcntl_WriteLock, self).__init__()
167
 
            # Check we can grab a lock before we actually open the file.
168
 
            self.filename = osutils.realpath(filename)
169
 
            if self.filename in _fcntl_WriteLock._open_locks:
170
 
                self._clear_f()
171
 
                raise errors.LockContention(self.filename)
172
 
 
173
 
            self._open(self.filename, 'rb+')
174
 
            # reserve a slot for this lock - even if the lockf call fails,
175
 
            # at thisi point unlock() will be called, because self.f is set.
176
 
            # TODO: make this fully threadsafe, if we decide we care.
177
 
            _fcntl_WriteLock._open_locks.add(self.filename)
 
90
            # standard IO errors get exposed directly.
 
91
            self._open(filename, 'wb')
178
92
            try:
179
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
180
 
                # lock right away.
181
 
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
93
                fcntl.lockf(self.f, fcntl.LOCK_EX)
182
94
            except IOError, e:
183
 
                if e.errno in (errno.EAGAIN, errno.EACCES):
184
 
                    # We couldn't grab the lock
185
 
                    self.unlock()
186
95
                # we should be more precise about whats a locking
187
96
                # error and whats a random-other error
188
 
                raise errors.LockContention(e)
189
 
 
190
 
        def unlock(self):
191
 
            _fcntl_WriteLock._open_locks.remove(self.filename)
192
 
            self._unlock()
193
 
 
 
97
                raise LockError(e)
194
98
 
195
99
    class _fcntl_ReadLock(_fcntl_FileLock):
196
100
 
197
 
        _open_locks = {}
198
 
 
199
101
        def __init__(self, filename):
200
 
            super(_fcntl_ReadLock, self).__init__()
201
 
            self.filename = osutils.realpath(filename)
202
 
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
203
 
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
102
            # standard IO errors get exposed directly.
204
103
            self._open(filename, 'rb')
205
104
            try:
206
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
207
 
                # lock right away.
208
 
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
105
                fcntl.lockf(self.f, fcntl.LOCK_SH)
209
106
            except IOError, e:
210
107
                # we should be more precise about whats a locking
211
108
                # error and whats a random-other error
212
 
                raise errors.LockContention(e)
213
 
 
214
 
        def unlock(self):
215
 
            count = _fcntl_ReadLock._open_locks[self.filename]
216
 
            if count == 1:
217
 
                del _fcntl_ReadLock._open_locks[self.filename]
218
 
            else:
219
 
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
220
 
            self._unlock()
221
 
 
222
 
        def temporary_write_lock(self):
223
 
            """Try to grab a write lock on the file.
224
 
 
225
 
            On platforms that support it, this will upgrade to a write lock
226
 
            without unlocking the file.
227
 
            Otherwise, this will release the read lock, and try to acquire a
228
 
            write lock.
229
 
 
230
 
            :return: A token which can be used to switch back to a read lock.
231
 
            """
232
 
            if self.filename in _fcntl_WriteLock._open_locks:
233
 
                raise AssertionError('file already locked: %r'
234
 
                    % (self.filename,))
235
 
            try:
236
 
                wlock = _fcntl_TemporaryWriteLock(self)
237
 
            except errors.LockError:
238
 
                # We didn't unlock, so we can just return 'self'
239
 
                return False, self
240
 
            return True, wlock
241
 
 
242
 
 
243
 
    class _fcntl_TemporaryWriteLock(_OSLock):
244
 
        """A token used when grabbing a temporary_write_lock.
245
 
 
246
 
        Call restore_read_lock() when you are done with the write lock.
247
 
        """
248
 
 
249
 
        def __init__(self, read_lock):
250
 
            super(_fcntl_TemporaryWriteLock, self).__init__()
251
 
            self._read_lock = read_lock
252
 
            self.filename = read_lock.filename
253
 
 
254
 
            count = _fcntl_ReadLock._open_locks[self.filename]
255
 
            if count > 1:
256
 
                # Something else also has a read-lock, so we cannot grab a
257
 
                # write lock.
258
 
                raise errors.LockContention(self.filename)
259
 
 
260
 
            if self.filename in _fcntl_WriteLock._open_locks:
261
 
                raise AssertionError('file already locked: %r'
262
 
                    % (self.filename,))
263
 
 
264
 
            # See if we can open the file for writing. Another process might
265
 
            # have a read lock. We don't use self._open() because we don't want
266
 
            # to create the file if it exists. That would have already been
267
 
            # done by _fcntl_ReadLock
268
 
            try:
269
 
                new_f = open(self.filename, 'rb+')
270
 
            except IOError, e:
271
 
                if e.errno in (errno.EACCES, errno.EPERM):
272
 
                    raise errors.LockFailed(self.filename, str(e))
273
 
                raise
274
 
            try:
275
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
276
 
                # lock right away.
277
 
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
278
 
            except IOError, e:
279
 
                # TODO: Raise a more specific error based on the type of error
280
 
                raise errors.LockContention(e)
281
 
            _fcntl_WriteLock._open_locks.add(self.filename)
282
 
 
283
 
            self.f = new_f
284
 
 
285
 
        def restore_read_lock(self):
286
 
            """Restore the original ReadLock."""
287
 
            # For fcntl, since we never released the read lock, just release the
288
 
            # write lock, and return the original lock.
289
 
            fcntl.lockf(self.f, fcntl.LOCK_UN)
290
 
            self._clear_f()
291
 
            _fcntl_WriteLock._open_locks.remove(self.filename)
292
 
            # Avoid reference cycles
293
 
            read_lock = self._read_lock
294
 
            self._read_lock = None
295
 
            return read_lock
296
 
 
297
 
 
298
 
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
299
 
 
300
 
 
301
 
if have_pywin32 and sys.platform == 'win32':
302
 
    LOCK_SH = 0 # the default
303
 
    LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
304
 
    LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
305
 
 
306
 
 
307
 
    class _w32c_FileLock(_OSLock):
308
 
 
309
 
        def _lock(self, filename, openmode, lockmode):
310
 
            self._open(filename, openmode)
311
 
 
312
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
313
 
            overlapped = pywintypes.OVERLAPPED()
314
 
            try:
315
 
                win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000,
316
 
                                     overlapped)
317
 
            except pywintypes.error, e:
318
 
                self._clear_f()
319
 
                if e.args[0] in (winerror.ERROR_LOCK_VIOLATION,):
320
 
                    raise errors.LockContention(filename)
321
 
                ## import pdb; pdb.set_trace()
322
 
                raise
323
 
            except Exception, e:
324
 
                self._clear_f()
325
 
                raise errors.LockContention(e)
326
 
 
327
 
        def unlock(self):
328
 
            overlapped = pywintypes.OVERLAPPED()
329
 
            try:
330
 
                win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
331
 
                self._clear_f()
332
 
            except Exception, e:
333
 
                raise errors.LockContention(e)
334
 
 
335
 
 
336
 
    class _w32c_ReadLock(_w32c_FileLock):
337
 
        def __init__(self, filename):
338
 
            super(_w32c_ReadLock, self).__init__()
339
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
340
 
 
341
 
        def temporary_write_lock(self):
342
 
            """Try to grab a write lock on the file.
343
 
 
344
 
            On platforms that support it, this will upgrade to a write lock
345
 
            without unlocking the file.
346
 
            Otherwise, this will release the read lock, and try to acquire a
347
 
            write lock.
348
 
 
349
 
            :return: A token which can be used to switch back to a read lock.
350
 
            """
351
 
            # I can't find a way to upgrade a read lock to a write lock without
352
 
            # unlocking first. So here, we do just that.
353
 
            self.unlock()
354
 
            try:
355
 
                wlock = _w32c_WriteLock(self.filename)
356
 
            except errors.LockError:
357
 
                return False, _w32c_ReadLock(self.filename)
358
 
            return True, wlock
359
 
 
360
 
 
361
 
    class _w32c_WriteLock(_w32c_FileLock):
362
 
        def __init__(self, filename):
363
 
            super(_w32c_WriteLock, self).__init__()
364
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
365
 
 
366
 
        def restore_read_lock(self):
367
 
            """Restore the original ReadLock."""
368
 
            # For win32 we had to completely let go of the original lock, so we
369
 
            # just unlock and create a new read lock.
370
 
            self.unlock()
371
 
            return _w32c_ReadLock(self.filename)
372
 
 
373
 
 
374
 
    _lock_classes.append(('pywin32', _w32c_WriteLock, _w32c_ReadLock))
375
 
 
376
 
 
377
 
if have_ctypes_win32:
378
 
    # These constants were copied from the win32con.py module.
379
 
    LOCKFILE_FAIL_IMMEDIATELY = 1
380
 
    LOCKFILE_EXCLUSIVE_LOCK = 2
381
 
    # Constant taken from winerror.py module
382
 
    ERROR_LOCK_VIOLATION = 33
383
 
 
384
 
    LOCK_SH = 0
385
 
    LOCK_EX = LOCKFILE_EXCLUSIVE_LOCK
386
 
    LOCK_NB = LOCKFILE_FAIL_IMMEDIATELY
387
 
    _LockFileEx = ctypes.windll.kernel32.LockFileEx
388
 
    _UnlockFileEx = ctypes.windll.kernel32.UnlockFileEx
389
 
    _GetLastError = ctypes.windll.kernel32.GetLastError
390
 
 
391
 
    ### Define the OVERLAPPED structure.
392
 
    #   http://msdn2.microsoft.com/en-us/library/ms684342.aspx
393
 
    # typedef struct _OVERLAPPED {
394
 
    #   ULONG_PTR Internal;
395
 
    #   ULONG_PTR InternalHigh;
396
 
    #   union {
397
 
    #     struct {
398
 
    #       DWORD Offset;
399
 
    #       DWORD OffsetHigh;
400
 
    #     };
401
 
    #     PVOID Pointer;
402
 
    #   };
403
 
    #   HANDLE hEvent;
404
 
    # } OVERLAPPED,
405
 
 
406
 
    class _inner_struct(ctypes.Structure):
407
 
        _fields_ = [('Offset', ctypes.c_uint), # DWORD
408
 
                    ('OffsetHigh', ctypes.c_uint), # DWORD
409
 
                   ]
410
 
 
411
 
    class _inner_union(ctypes.Union):
412
 
        _fields_  = [('anon_struct', _inner_struct), # struct
413
 
                     ('Pointer', ctypes.c_void_p), # PVOID
414
 
                    ]
415
 
 
416
 
    class OVERLAPPED(ctypes.Structure):
417
 
        _fields_ = [('Internal', ctypes.c_void_p), # ULONG_PTR
418
 
                    ('InternalHigh', ctypes.c_void_p), # ULONG_PTR
419
 
                    ('_inner_union', _inner_union),
420
 
                    ('hEvent', ctypes.c_void_p), # HANDLE
421
 
                   ]
422
 
 
423
 
    class _ctypes_FileLock(_OSLock):
424
 
 
425
 
        def _lock(self, filename, openmode, lockmode):
426
 
            self._open(filename, openmode)
427
 
 
428
 
            self.hfile = msvcrt.get_osfhandle(self.f.fileno())
429
 
            overlapped = OVERLAPPED()
430
 
            result = _LockFileEx(self.hfile, # HANDLE hFile
431
 
                                 lockmode,   # DWORD dwFlags
432
 
                                 0,          # DWORD dwReserved
433
 
                                 0x7fffffff, # DWORD nNumberOfBytesToLockLow
434
 
                                 0x00000000, # DWORD nNumberOfBytesToLockHigh
435
 
                                 ctypes.byref(overlapped), # lpOverlapped
436
 
                                )
437
 
            if result == 0:
438
 
                self._clear_f()
439
 
                last_err = _GetLastError()
440
 
                if last_err in (ERROR_LOCK_VIOLATION,):
441
 
                    raise errors.LockContention(filename)
442
 
                raise errors.LockContention('Unknown locking error: %s'
443
 
                                            % (last_err,))
444
 
 
445
 
        def unlock(self):
446
 
            overlapped = OVERLAPPED()
447
 
            result = _UnlockFileEx(self.hfile, # HANDLE hFile
448
 
                                   0,          # DWORD dwReserved
449
 
                                   0x7fffffff, # DWORD nNumberOfBytesToLockLow
450
 
                                   0x00000000, # DWORD nNumberOfBytesToLockHigh
451
 
                                   ctypes.byref(overlapped), # lpOverlapped
452
 
                                  )
453
 
            self._clear_f()
454
 
            if result == 0:
455
 
                self._clear_f()
456
 
                last_err = _GetLastError()
457
 
                raise errors.LockContention('Unknown unlocking error: %s'
458
 
                                            % (last_err,))
459
 
 
460
 
 
461
 
    class _ctypes_ReadLock(_ctypes_FileLock):
462
 
        def __init__(self, filename):
463
 
            super(_ctypes_ReadLock, self).__init__()
464
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
465
 
 
466
 
        def temporary_write_lock(self):
467
 
            """Try to grab a write lock on the file.
468
 
 
469
 
            On platforms that support it, this will upgrade to a write lock
470
 
            without unlocking the file.
471
 
            Otherwise, this will release the read lock, and try to acquire a
472
 
            write lock.
473
 
 
474
 
            :return: A token which can be used to switch back to a read lock.
475
 
            """
476
 
            # I can't find a way to upgrade a read lock to a write lock without
477
 
            # unlocking first. So here, we do just that.
478
 
            self.unlock()
479
 
            try:
480
 
                wlock = _ctypes_WriteLock(self.filename)
481
 
            except errors.LockError:
482
 
                return False, _ctypes_ReadLock(self.filename)
483
 
            return True, wlock
484
 
 
485
 
    class _ctypes_WriteLock(_ctypes_FileLock):
486
 
        def __init__(self, filename):
487
 
            super(_ctypes_WriteLock, self).__init__()
488
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
489
 
 
490
 
        def restore_read_lock(self):
491
 
            """Restore the original ReadLock."""
492
 
            # For win32 we had to completely let go of the original lock, so we
493
 
            # just unlock and create a new read lock.
494
 
            self.unlock()
495
 
            return _ctypes_ReadLock(self.filename)
496
 
 
497
 
 
498
 
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
499
 
 
500
 
 
501
 
if len(_lock_classes) == 0:
502
 
    raise NotImplementedError(
503
 
        "We must have one of fcntl, pywin32, or ctypes available"
504
 
        " to support OS locking."
505
 
        )
506
 
 
507
 
 
508
 
# We default to using the first available lock class.
509
 
_lock_type, WriteLock, ReadLock = _lock_classes[0]
510
 
 
 
109
                raise LockError(e)
 
110
 
 
111
    WriteLock = _fcntl_WriteLock
 
112
    ReadLock = _fcntl_ReadLock
 
113
 
 
114
 
 
115
except ImportError:
 
116
    try:
 
117
        import win32con, win32file, pywintypes
 
118
 
 
119
 
 
120
        LOCK_SH = 0 # the default
 
121
        LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
122
        LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
123
 
 
124
        class _w32c_FileLock(_base_Lock):
 
125
            def _lock(self, filename, openmode, lockmode):
 
126
                try:
 
127
                    self._open(filename, openmode)
 
128
                    self.hfile = win32file._get_osfhandle(self.f.fileno())
 
129
                    overlapped = pywintypes.OVERLAPPED()
 
130
                    win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
 
131
                except Exception, e:
 
132
                    raise LockError(e)
 
133
 
 
134
            def unlock(self):
 
135
                try:
 
136
                    overlapped = pywintypes.OVERLAPPED()
 
137
                    win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
138
                    self.f.close()
 
139
                    self.f = None
 
140
                except Exception, e:
 
141
                    raise LockError(e)
 
142
 
 
143
 
 
144
 
 
145
        class _w32c_ReadLock(_w32c_FileLock):
 
146
            def __init__(self, filename):
 
147
                _w32c_FileLock._lock(self, filename, 'rb',
 
148
                                     LOCK_NB)
 
149
 
 
150
        class _w32c_WriteLock(_w32c_FileLock):
 
151
            def __init__(self, filename):
 
152
                _w32c_FileLock._lock(self, filename, 'wb',
 
153
                                     LOCK_EX + LOCK_NB)
 
154
 
 
155
 
 
156
 
 
157
        WriteLock = _w32c_WriteLock
 
158
        ReadLock = _w32c_ReadLock
 
159
 
 
160
    except ImportError:
 
161
        try:
 
162
            import msvcrt
 
163
 
 
164
 
 
165
            # Unfortunately, msvcrt.locking() doesn't distinguish between
 
166
            # read locks and write locks. Also, the way the combinations
 
167
            # work to get non-blocking is not the same, so we
 
168
            # have to write extra special functions here.
 
169
 
 
170
 
 
171
            class _msvc_FileLock(_base_Lock):
 
172
                LOCK_SH = 1
 
173
                LOCK_EX = 2
 
174
                LOCK_NB = 4
 
175
                def unlock(self):
 
176
                    _msvc_unlock(self.f)
 
177
                    self.f.close()
 
178
                    self.f = None
 
179
 
 
180
 
 
181
            class _msvc_ReadLock(_msvc_FileLock):
 
182
                def __init__(self, filename):
 
183
                    _msvc_lock(self._open(filename, 'rb'), self.LOCK_SH)
 
184
 
 
185
 
 
186
            class _msvc_WriteLock(_msvc_FileLock):
 
187
                def __init__(self, filename):
 
188
                    _msvc_lock(self._open(filename, 'wb'), self.LOCK_EX)
 
189
 
 
190
 
 
191
 
 
192
            def _msvc_lock(f, flags):
 
193
                try:
 
194
                    # Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
 
195
                    # according to the comments, LK_RLCK is open the lock for writing.
 
196
 
 
197
                    # Unfortunately, msvcrt.locking() also has the side effect that it
 
198
                    # will only block for 10 seconds at most, and then it will throw an
 
199
                    # exception, this isn't terrible, though.
 
200
                    if type(f) == file:
 
201
                        fpos = f.tell()
 
202
                        fn = f.fileno()
 
203
                        f.seek(0)
 
204
                    else:
 
205
                        fn = f
 
206
                        fpos = os.lseek(fn, 0,0)
 
207
                        os.lseek(fn, 0,0)
 
208
 
 
209
                    if flags & _msvc_FileLock.LOCK_SH:
 
210
                        if flags & _msvc_FileLock.LOCK_NB:
 
211
                            lock_mode = msvcrt.LK_NBLCK
 
212
                        else:
 
213
                            lock_mode = msvcrt.LK_LOCK
 
214
                    elif flags & _msvc_FileLock.LOCK_EX:
 
215
                        if flags & _msvc_FileLock.LOCK_NB:
 
216
                            lock_mode = msvcrt.LK_NBRLCK
 
217
                        else:
 
218
                            lock_mode = msvcrt.LK_RLCK
 
219
                    else:
 
220
                        raise ValueError('Invalid lock mode: %r' % flags)
 
221
                    try:
 
222
                        msvcrt.locking(fn, lock_mode, -1)
 
223
                    finally:
 
224
                        os.lseek(fn, fpos, 0)
 
225
                except Exception, e:
 
226
                    raise LockError(e)
 
227
 
 
228
            def _msvc_unlock(f):
 
229
                try:
 
230
                    if type(f) == file:
 
231
                        fpos = f.tell()
 
232
                        fn = f.fileno()
 
233
                        f.seek(0)
 
234
                    else:
 
235
                        fn = f
 
236
                        fpos = os.lseek(fn, 0,0)
 
237
                        os.lseek(fn, 0,0)
 
238
 
 
239
                    try:
 
240
                        msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
 
241
                    finally:
 
242
                        os.lseek(fn, fpos, 0)
 
243
                except Exception, e:
 
244
                    raise LockError(e)
 
245
 
 
246
 
 
247
 
 
248
            WriteLock = _msvc_WriteLock
 
249
            ReadLock = _msvc_ReadLock
 
250
        except ImportError:
 
251
            raise NotImplementedError("please write a locking method "
 
252
                                      "for platform %r" % sys.platform)