/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

Define an explicit error when trying to grab a write lock on a readonly file.
Add some future tests that ensure write and read locks exclude eachother.
Currently fcntl locks do not exclude.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
"""Locking using OS file locks or file existence.
 
19
 
 
20
Note: This method of locking is generally deprecated in favour of LockDir, but
 
21
is used to lock local WorkingTrees, and by some old formats.  It's accessed
 
22
through Transport.lock_read(), etc.
 
23
 
 
24
This module causes two methods, lock() and unlock() to be defined in
 
25
any way that works on the current platform.
 
26
 
 
27
It is not specified whether these locks are reentrant (i.e. can be
 
28
taken repeatedly by a single process) or whether they exclude
 
29
different threads in a single process.  That reentrancy is provided by 
 
30
LockableFiles.
 
31
 
 
32
This defines two classes: ReadLock and WriteLock, which can be
 
33
implemented in different ways on different platforms.  Both have an
 
34
unlock() method.
 
35
"""
 
36
 
 
37
import errno
 
38
import os
 
39
import sys
 
40
 
 
41
from bzrlib import errors
 
42
from bzrlib.errors import LockError, LockContention
 
43
from bzrlib.osutils import realpath
 
44
from bzrlib.trace import mutter
 
45
 
 
46
 
 
47
class _base_Lock(object):
 
48
 
 
49
    def _open(self, filename, filemode):
 
50
        try:
 
51
            self.f = open(filename, filemode)
 
52
            return self.f
 
53
        except IOError, e:
 
54
            if e.errno != errno.ENOENT:
 
55
                raise
 
56
 
 
57
            # maybe this is an old branch (before may 2005)
 
58
            mutter("trying to create missing branch lock %r", filename)
 
59
            
 
60
            self.f = open(filename, 'wb+')
 
61
            return self.f
 
62
 
 
63
    def __del__(self):
 
64
        if self.f:
 
65
            from warnings import warn
 
66
            warn("lock on %r not released" % self.f)
 
67
            self.unlock()
 
68
            
 
69
    def unlock(self):
 
70
        raise NotImplementedError()
 
71
 
 
72
 
 
73
############################################################
 
74
# msvcrt locks
 
75
 
 
76
 
 
77
try:
 
78
    import fcntl
 
79
 
 
80
    class _fcntl_FileLock(_base_Lock):
 
81
 
 
82
        f = None
 
83
 
 
84
        def _unlock(self):
 
85
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
86
            self._clear_f()
 
87
 
 
88
        def _clear_f(self):
 
89
            """Clear the self.f attribute cleanly."""
 
90
            self.f.close()
 
91
            del self.f
 
92
 
 
93
 
 
94
    class _fcntl_WriteLock(_fcntl_FileLock):
 
95
 
 
96
        open_locks = {}
 
97
 
 
98
        def __init__(self, filename):
 
99
            # standard IO errors get exposed directly.
 
100
            try:
 
101
                self._open(filename, 'rb+')
 
102
            except IOError, e:
 
103
                if e.errno in (errno.EACCES, errno.EPERM):
 
104
                    raise errors.ReadOnlyLockError(e)
 
105
                raise
 
106
            self.filename = realpath(filename)
 
107
            if self.filename in self.open_locks:
 
108
                self._clear_f()
 
109
                raise LockContention("Lock already held.")
 
110
            # reserve a slot for this lock - even if the lockf call fails,
 
111
            # at thisi point unlock() will be called, because self.f is set.
 
112
            # TODO: make this fully threadsafe, if we decide we care.
 
113
            self.open_locks[self.filename] = self.filename
 
114
            try:
 
115
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
116
                # lock right away.
 
117
                fcntl.lockf(self.f, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
118
            except IOError, e:
 
119
                if e.errno in (errno.EAGAIN, errno.EACCES):
 
120
                    # We couldn't grab the lock
 
121
                    self.unlock()
 
122
                # we should be more precise about whats a locking
 
123
                # error and whats a random-other error
 
124
                raise LockError(e)
 
125
 
 
126
        def unlock(self):
 
127
            del self.open_locks[self.filename]
 
128
            self._unlock()
 
129
 
 
130
 
 
131
    class _fcntl_ReadLock(_fcntl_FileLock):
 
132
 
 
133
        open_locks = {}
 
134
 
 
135
        def __init__(self, filename):
 
136
            self._open(filename, 'rb')
 
137
            try:
 
138
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
139
                # lock right away.
 
140
                fcntl.lockf(self.f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
141
            except IOError, e:
 
142
                # we should be more precise about whats a locking
 
143
                # error and whats a random-other error
 
144
                raise LockError(e)
 
145
 
 
146
        def unlock(self):
 
147
            self._unlock()
 
148
 
 
149
 
 
150
    WriteLock = _fcntl_WriteLock
 
151
    ReadLock = _fcntl_ReadLock
 
152
 
 
153
 
 
154
except ImportError:
 
155
    try:
 
156
        import win32con, win32file, pywintypes
 
157
 
 
158
 
 
159
        LOCK_SH = 0 # the default
 
160
        LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
 
161
        LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
 
162
 
 
163
        class _w32c_FileLock(_base_Lock):
 
164
            def _lock(self, filename, openmode, lockmode):
 
165
                try:
 
166
                    self._open(filename, openmode)
 
167
                    self.hfile = win32file._get_osfhandle(self.f.fileno())
 
168
                    overlapped = pywintypes.OVERLAPPED()
 
169
                    win32file.LockFileEx(self.hfile, lockmode, 0, 0x7fff0000, overlapped)
 
170
                except Exception, e:
 
171
                    if self.f:
 
172
                        self.f.close()
 
173
                        self.f = None
 
174
                    raise LockError(e)
 
175
 
 
176
            def unlock(self):
 
177
                try:
 
178
                    overlapped = pywintypes.OVERLAPPED()
 
179
                    win32file.UnlockFileEx(self.hfile, 0, 0x7fff0000, overlapped)
 
180
                    self.f.close()
 
181
                    self.f = None
 
182
                except Exception, e:
 
183
                    raise LockError(e)
 
184
 
 
185
 
 
186
        class _w32c_ReadLock(_w32c_FileLock):
 
187
            def __init__(self, filename):
 
188
                _w32c_FileLock._lock(self, filename, 'rb',
 
189
                                     LOCK_NB)
 
190
 
 
191
        class _w32c_WriteLock(_w32c_FileLock):
 
192
            def __init__(self, filename):
 
193
                _w32c_FileLock._lock(self, filename, 'rb+',
 
194
                                     LOCK_EX + LOCK_NB)
 
195
 
 
196
 
 
197
        WriteLock = _w32c_WriteLock
 
198
        ReadLock = _w32c_ReadLock
 
199
 
 
200
    except ImportError:
 
201
        try:
 
202
            import msvcrt
 
203
 
 
204
 
 
205
            # Unfortunately, msvcrt.locking() doesn't distinguish between
 
206
            # read locks and write locks. Also, the way the combinations
 
207
            # work to get non-blocking is not the same, so we
 
208
            # have to write extra special functions here.
 
209
 
 
210
 
 
211
            class _msvc_FileLock(_base_Lock):
 
212
                LOCK_SH = 1
 
213
                LOCK_EX = 2
 
214
                LOCK_NB = 4
 
215
 
 
216
                def unlock(self):
 
217
                    _msvc_unlock(self.f)
 
218
                    self.f.close()
 
219
                    self.f = None
 
220
 
 
221
 
 
222
            class _msvc_ReadLock(_msvc_FileLock):
 
223
                def __init__(self, filename):
 
224
                    _msvc_lock(self._open(filename, 'rb'),
 
225
                               self.LOCK_SH | self.LOCK_NB)
 
226
 
 
227
 
 
228
            class _msvc_WriteLock(_msvc_FileLock):
 
229
                def __init__(self, filename):
 
230
                    _msvc_lock(self._open(filename, 'rb+'),
 
231
                               self.LOCK_EX | self.LOCK_NB)
 
232
 
 
233
 
 
234
            def _msvc_lock(f, flags):
 
235
                try:
 
236
                    # Unfortunately, msvcrt.LK_RLCK is equivalent to msvcrt.LK_LOCK
 
237
                    # according to the comments, LK_RLCK is open the lock for writing.
 
238
 
 
239
                    # Unfortunately, msvcrt.locking() also has the side effect that it
 
240
                    # will only block for 10 seconds at most, and then it will throw an
 
241
                    # exception, this isn't terrible, though.
 
242
                    if type(f) == file:
 
243
                        fpos = f.tell()
 
244
                        fn = f.fileno()
 
245
                        f.seek(0)
 
246
                    else:
 
247
                        fn = f
 
248
                        fpos = os.lseek(fn, 0,0)
 
249
                        os.lseek(fn, 0,0)
 
250
 
 
251
                    if flags & _msvc_FileLock.LOCK_SH:
 
252
                        if flags & _msvc_FileLock.LOCK_NB:
 
253
                            lock_mode = msvcrt.LK_NBLCK
 
254
                        else:
 
255
                            lock_mode = msvcrt.LK_LOCK
 
256
                    elif flags & _msvc_FileLock.LOCK_EX:
 
257
                        if flags & _msvc_FileLock.LOCK_NB:
 
258
                            lock_mode = msvcrt.LK_NBRLCK
 
259
                        else:
 
260
                            lock_mode = msvcrt.LK_RLCK
 
261
                    else:
 
262
                        raise ValueError('Invalid lock mode: %r' % flags)
 
263
                    try:
 
264
                        msvcrt.locking(fn, lock_mode, -1)
 
265
                    finally:
 
266
                        os.lseek(fn, fpos, 0)
 
267
                except Exception, e:
 
268
                    raise LockError(e)
 
269
 
 
270
            def _msvc_unlock(f):
 
271
                try:
 
272
                    if type(f) == file:
 
273
                        fpos = f.tell()
 
274
                        fn = f.fileno()
 
275
                        f.seek(0)
 
276
                    else:
 
277
                        fn = f
 
278
                        fpos = os.lseek(fn, 0,0)
 
279
                        os.lseek(fn, 0,0)
 
280
 
 
281
                    try:
 
282
                        msvcrt.locking(fn, msvcrt.LK_UNLCK, -1)
 
283
                    finally:
 
284
                        os.lseek(fn, fpos, 0)
 
285
                except Exception, e:
 
286
                    raise LockError(e)
 
287
 
 
288
 
 
289
            WriteLock = _msvc_WriteLock
 
290
            ReadLock = _msvc_ReadLock
 
291
        except ImportError:
 
292
            raise NotImplementedError("please write a locking method "
 
293
                                      "for platform %r" % sys.platform)