~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lock.py

Implement a 'ReadLock.temporary_write_lock()' to upgrade to a write-lock in-process.
Implement it for _fcntl.
This is the api that can be used when we want to update a dirty dirstate
even though we only have a read lock.
On win32, this will actually unlock and re-lock the file. Which is part of
the api description. But we don't have to limit ourselves to the lowest
common denominator on all platforms.

Show diffs side-by-side

added added

removed removed

Lines of Context:
154
154
 
155
155
        _open_locks = {}
156
156
 
157
 
        def __init__(self, filename):
 
157
        def __init__(self, filename, _ignore_write_lock=False):
158
158
            super(_fcntl_ReadLock, self).__init__()
159
159
            self.filename = osutils.realpath(filename)
160
 
            if self.filename in _fcntl_WriteLock._open_locks:
 
160
            if not _ignore_write_lock and self.filename in _fcntl_WriteLock._open_locks:
161
161
                raise errors.LockContention(self.filename)
162
162
            _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
163
163
            _fcntl_ReadLock._open_locks[self.filename] += 1
179
179
                _fcntl_ReadLock._open_locks[self.filename] = count - 1
180
180
            self._unlock()
181
181
 
 
182
        def temporary_write_lock(self):
 
183
            """Try to grab a write lock on the file.
 
184
 
 
185
            On platforms that support it, this will upgrade to a write lock
 
186
            without unlocking the file.
 
187
            Otherwise, this will release the read lock, and try to acquire a
 
188
            write lock.
 
189
 
 
190
            :return: A token which can be used to switch back to a read lock.
 
191
            """
 
192
            assert self.filename not in _fcntl_WriteLock._open_locks
 
193
            return _fcntl_TemporaryWriteLock(self)
 
194
 
 
195
 
 
196
    class _fcntl_TemporaryWriteLock(_base_Lock):
 
197
        """A token used when grabbing a temporary_write_lock.
 
198
 
 
199
        Call restore_read_lock() when you are done with the write lock.
 
200
        """
 
201
 
 
202
        def __init__(self, read_lock):
 
203
            super(_fcntl_TemporaryWriteLock, self).__init__()
 
204
            self._read_lock = read_lock
 
205
            self.filename = read_lock.filename
 
206
 
 
207
            count = _fcntl_ReadLock._open_locks[self.filename]
 
208
            if count > 1:
 
209
                # Something else also has a read-lock, so we cannot grab a
 
210
                # write lock.
 
211
                raise errors.LockContention(self.filename)
 
212
 
 
213
            assert self.filename not in _fcntl_WriteLock._open_locks
 
214
 
 
215
            # See if we can open the file for writing. Another process might
 
216
            # have a read lock. We don't use self._open() because we don't want
 
217
            # to create the file if it exists. That would have already been
 
218
            # done by _fcntl_ReadLock
 
219
            try:
 
220
                new_f = open(self.filename, 'rb+')
 
221
            except IOError, e:
 
222
                if e.errno in (errno.EACCES, errno.EPERM):
 
223
                    raise errors.ReadOnlyLockError(self.filename, str(e))
 
224
                raise
 
225
            try:
 
226
                # LOCK_NB will cause IOError to be raised if we can't grab a
 
227
                # lock right away.
 
228
                fcntl.lockf(new_f, fcntl.LOCK_SH | fcntl.LOCK_NB)
 
229
            except IOError, e:
 
230
                # TODO: Raise a more specific error based on the type of error
 
231
                raise errors.LockError(e)
 
232
            _fcntl_WriteLock._open_locks.add(self.filename)
 
233
 
 
234
            self.f = new_f
 
235
 
 
236
        def restore_read_lock(self):
 
237
            """Restore the original ReadLock.
 
238
 
 
239
            For fcntl, since we never released the read lock, just release the
 
240
            write lock, and return the original lock.
 
241
            """
 
242
            fcntl.lockf(self.f, fcntl.LOCK_UN)
 
243
            self._clear_f()
 
244
            _fcntl_WriteLock._open_locks.remove(self.filename)
 
245
            # Avoid reference cycles
 
246
            read_lock = self._read_lock
 
247
            self._read_lock = None
 
248
            return read_lock
 
249
 
182
250
 
183
251
    _lock_classes.append(('fcntl', _fcntl_WriteLock, _fcntl_ReadLock))
184
252