~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

  • Committer: Martin Pool
  • Date: 2006-03-20 23:09:42 UTC
  • mto: This revision was merged to the branch mainline in revision 1621.
  • Revision ID: mbp@sourcefrog.net-20060320230942-152767f76202f543
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 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
 
 
46
 
 
47
 
class _OSLock(object):
48
 
 
49
 
    def __init__(self):
50
 
        self.f = None
51
 
        self.filename = None
52
 
 
 
41
from bzrlib.trace import mutter, note, warning
 
42
from bzrlib.errors import LockError
 
43
 
 
44
class _base_Lock(object):
53
45
    def _open(self, filename, filemode):
54
 
        self.filename = osutils.realpath(filename)
55
46
        try:
56
 
            self.f = open(self.filename, filemode)
 
47
            self.f = open(filename, filemode)
57
48
            return self.f
58
49
        except IOError, e:
59
 
            if e.errno in (errno.EACCES, errno.EPERM):
60
 
                raise errors.ReadOnlyLockError(self.filename, str(e))
61
50
            if e.errno != errno.ENOENT:
62
51
                raise
63
52
 
64
53
            # maybe this is an old branch (before may 2005)
65
 
            trace.mutter("trying to create missing lock %r", self.filename)
66
 
 
67
 
            self.f = open(self.filename, 'wb+')
 
54
            mutter("trying to create missing branch lock %r", filename)
 
55
            
 
56
            self.f = open(filename, 'wb+')
68
57
            return self.f
69
58
 
70
 
    def _clear_f(self):
71
 
        """Clear the self.f attribute cleanly."""
72
 
        if self.f:
73
 
            self.f.close()
74
 
            self.f = None
75
 
 
76
59
    def __del__(self):
77
60
        if self.f:
78
61
            from warnings import warn
79
62
            warn("lock on %r not released" % self.f)
80
63
            self.unlock()
81
 
 
 
64
            
82
65
    def unlock(self):
83
66
        raise NotImplementedError()
84
67
 
 
68
        
 
69
 
 
70
 
 
71
 
 
72
 
 
73
############################################################
 
74
# msvcrt locks
 
75
 
85
76
 
86
77
try:
87
78
    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
 
 
103
 
_lock_classes = []
104
 
 
105
 
 
106
 
if have_fcntl:
107
 
    LOCK_SH = fcntl.LOCK_SH
108
 
    LOCK_NB = fcntl.LOCK_NB
109
 
    lock_EX = fcntl.LOCK_EX
110
 
 
111
 
 
112
 
    class _fcntl_FileLock(_OSLock):
113
 
 
114
 
        def _unlock(self):
 
79
 
 
80
    class _fcntl_FileLock(_base_Lock):
 
81
        f = None
 
82
 
 
83
        def unlock(self):
115
84
            fcntl.lockf(self.f, fcntl.LOCK_UN)
116
 
            self._clear_f()
117
 
 
 
85
            self.f.close()
 
86
            del self.f 
118
87
 
119
88
    class _fcntl_WriteLock(_fcntl_FileLock):
120
 
 
121
 
        _open_locks = set()
122
 
 
123
89
        def __init__(self, filename):
124
 
            super(_fcntl_WriteLock, self).__init__()
125
 
            # Check we can grab a lock before we actually open the file.
126
 
            self.filename = osutils.realpath(filename)
127
 
            if self.filename in _fcntl_WriteLock._open_locks:
128
 
                self._clear_f()
129
 
                raise errors.LockContention(self.filename)
130
 
 
131
 
            self._open(self.filename, 'rb+')
132
 
            # reserve a slot for this lock - even if the lockf call fails,
133
 
            # at thisi point unlock() will be called, because self.f is set.
134
 
            # TODO: make this fully threadsafe, if we decide we care.
135
 
            _fcntl_WriteLock._open_locks.add(self.filename)
 
90
            # standard IO errors get exposed directly.
 
91
            self._open(filename, 'wb')
136
92
            try:
137
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
138
 
                # lock right away.
139
 
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
93
                fcntl.lockf(self.f, fcntl.LOCK_EX)
140
94
            except IOError, e:
141
 
                if e.errno in (errno.EAGAIN, errno.EACCES):
142
 
                    # We couldn't grab the lock
143
 
                    self.unlock()
144
95
                # we should be more precise about whats a locking
145
96
                # error and whats a random-other error
146
 
                raise errors.LockContention(e)
147
 
 
148
 
        def unlock(self):
149
 
            _fcntl_WriteLock._open_locks.remove(self.filename)
150
 
            self._unlock()
151
 
 
 
97
                raise LockError(e)
152
98
 
153
99
    class _fcntl_ReadLock(_fcntl_FileLock):
154
100
 
155
 
        _open_locks = {}
156
 
 
157
101
        def __init__(self, filename):
158
 
            super(_fcntl_ReadLock, self).__init__()
159
 
            self.filename = osutils.realpath(filename)
160
 
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
161
 
            _fcntl_ReadLock._open_locks[self.filename] += 1
 
102
            # standard IO errors get exposed directly.
162
103
            self._open(filename, 'rb')
163
104
            try:
164
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
165
 
                # lock right away.
166
 
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
105
                fcntl.lockf(self.f, fcntl.LOCK_SH)
167
106
            except IOError, e:
168
107
                # we should be more precise about whats a locking
169
108
                # error and whats a random-other error
170
 
                raise errors.LockContention(e)
171
 
 
172
 
        def unlock(self):
173
 
            count = _fcntl_ReadLock._open_locks[self.filename]
174
 
            if count == 1:
175
 
                del _fcntl_ReadLock._open_locks[self.filename]
176
 
            else:
177
 
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
178
 
            self._unlock()
179
 
 
180
 
        def temporary_write_lock(self):
181
 
            """Try to grab a write lock on the file.
182
 
 
183
 
            On platforms that support it, this will upgrade to a write lock
184
 
            without unlocking the file.
185
 
            Otherwise, this will release the read lock, and try to acquire a
186
 
            write lock.
187
 
 
188
 
            :return: A token which can be used to switch back to a read lock.
189
 
            """
190
 
            assert self.filename not in _fcntl_WriteLock._open_locks
191
 
            try:
192
 
                wlock = _fcntl_TemporaryWriteLock(self)
193
 
            except errors.LockError:
194
 
                # We didn't unlock, so we can just return 'self'
195
 
                return False, self
196
 
            return True, wlock
197
 
 
198
 
 
199
 
    class _fcntl_TemporaryWriteLock(_OSLock):
200
 
        """A token used when grabbing a temporary_write_lock.
201
 
 
202
 
        Call restore_read_lock() when you are done with the write lock.
203
 
        """
204
 
 
205
 
        def __init__(self, read_lock):
206
 
            super(_fcntl_TemporaryWriteLock, self).__init__()
207
 
            self._read_lock = read_lock
208
 
            self.filename = read_lock.filename
209
 
 
210
 
            count = _fcntl_ReadLock._open_locks[self.filename]
211
 
            if count > 1:
212
 
                # Something else also has a read-lock, so we cannot grab a
213
 
                # write lock.
214
 
                raise errors.LockContention(self.filename)
215
 
 
216
 
            assert self.filename not in _fcntl_WriteLock._open_locks
217
 
 
218
 
            # See if we can open the file for writing. Another process might
219
 
            # have a read lock. We don't use self._open() because we don't want
220
 
            # to create the file if it exists. That would have already been
221
 
            # done by _fcntl_ReadLock
222
 
            try:
223
 
                new_f = open(self.filename, 'rb+')
224
 
            except IOError, e:
225
 
                if e.errno in (errno.EACCES, errno.EPERM):
226
 
                    raise errors.ReadOnlyLockError(self.filename, str(e))
227
 
                raise
228
 
            try:
229
 
                # LOCK_NB will cause IOError to be raised if we can't grab a
230
 
                # lock right away.
231
 
                fcntl.lockf(new_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
232
 
            except IOError, e:
233
 
                # TODO: Raise a more specific error based on the type of error
234
 
                raise errors.LockContention(e)
235
 
            _fcntl_WriteLock._open_locks.add(self.filename)
236
 
 
237
 
            self.f = new_f
238
 
 
239
 
        def restore_read_lock(self):
240
 
            """Restore the original ReadLock."""
241
 
            # For fcntl, since we never released the read lock, just release the
242
 
            # write lock, and return the original lock.
243
 
            fcntl.lockf(self.f, fcntl.LOCK_UN)
244
 
            self._clear_f()
245
 
            _fcntl_WriteLock._open_locks.remove(self.filename)
246
 
            # Avoid reference cycles
247
 
            read_lock = self._read_lock
248
 
            self._read_lock = None
249
 
            return read_lock
250
 
 
251
 
 
252
 
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
253
 
 
254
 
 
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(_OSLock):
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(_OSLock):
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
 
                                  )
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
 
 
414
 
 
415
 
    class _ctypes_ReadLock(_ctypes_FileLock):
416
 
        def __init__(self, filename):
417
 
            super(_ctypes_ReadLock, self).__init__()
418
 
            self._lock(filename, 'rb', LOCK_SH + LOCK_NB)
419
 
 
420
 
        def temporary_write_lock(self):
421
 
            """Try to grab a write lock on the file.
422
 
 
423
 
            On platforms that support it, this will upgrade to a write lock
424
 
            without unlocking the file.
425
 
            Otherwise, this will release the read lock, and try to acquire a
426
 
            write lock.
427
 
 
428
 
            :return: A token which can be used to switch back to a read lock.
429
 
            """
430
 
            # I can't find a way to upgrade a read lock to a write lock without
431
 
            # unlocking first. So here, we do just that.
432
 
            self.unlock()
433
 
            try:
434
 
                wlock = _ctypes_WriteLock(self.filename)
435
 
            except errors.LockError:
436
 
                return False, _ctypes_ReadLock(self.filename)
437
 
            return True, wlock
438
 
 
439
 
    class _ctypes_WriteLock(_ctypes_FileLock):
440
 
        def __init__(self, filename):
441
 
            super(_ctypes_WriteLock, self).__init__()
442
 
            self._lock(filename, 'rb+', LOCK_EX + LOCK_NB)
443
 
 
444
 
        def restore_read_lock(self):
445
 
            """Restore the original ReadLock."""
446
 
            # For win32 we had to completely let go of the original lock, so we
447
 
            # just unlock and create a new read lock.
448
 
            self.unlock()
449
 
            return _ctypes_ReadLock(self.filename)
450
 
 
451
 
 
452
 
    _lock_classes.append(('ctypes', _ctypes_WriteLock, _ctypes_ReadLock))
453
 
 
454
 
 
455
 
if len(_lock_classes) == 0:
456
 
    raise NotImplementedError(
457
 
        "We must have one of fcntl, pywin32, or ctypes available"
458
 
        " to support OS locking."
459
 
        )
460
 
 
461
 
 
462
 
# We default to using the first available lock class.
463
 
_lock_type, WriteLock, ReadLock = _lock_classes[0]
464
 
 
 
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)