121
118
# lock at the same time they should *both* get it. But then that's unlikely
122
119
# to be a good idea.
121
# TODO: Transport could offer a simpler put() method that avoids the
122
# rename-into-place for cases like creating the lock template, where there is
123
# no chance that the file already exists.
124
125
# TODO: Perhaps store some kind of note like the bzr command line in the lock
127
128
# TODO: Some kind of callback run while polling a lock to show progress
130
# TODO: Make sure to pass the right file and directory mode bits to all
131
# files/dirs created.
133
131
_DEFAULT_TIMEOUT_SECONDS = 300
134
132
_DEFAULT_POLL_SECONDS = 0.5
168
162
is_held = property(lambda self: self._lock_held)
170
def create(self, mode=None):
171
"""Create the on-disk lock.
173
This is typically only called when the object/directory containing the
174
directory is first created. The lock is not held when it's created.
176
if self.transport.is_readonly():
177
raise UnlockableTransport(self.transport)
178
self.transport.mkdir(self.path, mode=mode)
180
164
def attempt_lock(self):
181
165
"""Take the lock; fail if it's already held.
183
167
If you wish to block until the lock can be obtained, call wait_lock()
186
if self._fake_read_lock:
187
raise LockContention(self)
188
170
if self.transport.is_readonly():
189
171
raise UnlockableTransport(self.transport)
191
tmpname = '%s/pending.%s.tmp' % (self.path, rand_chars(20))
173
tmpname = '%s.pending.%s.tmp' % (self.path, rand_chars(20))
192
174
self.transport.mkdir(tmpname)
194
176
self._prepare_info(sio)
196
# append will create a new file; we use append rather than put
197
# because we don't want to write to a temporary file and rename
198
# into place, because that's going to happen to the whole
200
self.transport.append(tmpname + self.__INFO_NAME, sio)
201
self.transport.rename(tmpname, self._held_dir)
178
self.transport.put(tmpname + self.__INFO_NAME, sio)
179
# FIXME: this turns into os.rename on posix, but into a fancy rename
180
# on Windows that may overwrite existing directory trees.
181
# NB: posix rename will overwrite empty directories, but not
182
# non-empty directories.
183
self.transport.move(tmpname, self.path)
202
184
self._lock_held = True
204
except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
205
mutter("contention on %r: %s", self, e)
206
raise LockContention(self)
187
except (DirectoryNotEmpty, FileExists), e:
189
# fall through to here on contention
190
raise LockContention(self)
208
192
def unlock(self):
209
193
"""Release a held lock
211
if self._fake_read_lock:
212
self._fake_read_lock = False
214
195
if not self._lock_held:
215
196
raise LockNotHeld(self)
216
197
# rename before deleting, because we can't atomically remove the whole
218
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
219
# gotta own it to unlock
221
self.transport.rename(self._held_dir, tmpname)
199
tmpname = '%s.releasing.%s.tmp' % (self.path, rand_chars(20))
200
self.transport.rename(self.path, tmpname)
222
201
self._lock_held = False
223
202
self.transport.delete(tmpname + self.__INFO_NAME)
224
203
self.transport.rmdir(tmpname)
226
def break_lock(self):
227
"""Break a lock not held by this instance of LockDir.
229
This is a UI centric function: it uses the bzrlib.ui.ui_factory to
230
prompt for input if a lock is detected and there is any doubt about
231
it possibly being still active.
233
self._check_not_locked()
234
holder_info = self.peek()
235
if holder_info is not None:
236
if bzrlib.ui.ui_factory.get_boolean(
237
"Break lock %s held by %s@%s [process #%s]" % (
240
holder_info["hostname"],
241
holder_info["pid"])):
242
self.force_break(holder_info)
244
205
def force_break(self, dead_holder_info):
245
206
"""Release a lock held by another process.
260
221
if not isinstance(dead_holder_info, dict):
261
222
raise ValueError("dead_holder_info: %r" % dead_holder_info)
262
self._check_not_locked()
224
raise AssertionError("can't break own lock: %r" % self)
263
225
current_info = self.peek()
264
226
if current_info is None:
265
227
# must have been recently released
267
229
if current_info != dead_holder_info:
268
230
raise LockBreakMismatch(self, current_info, dead_holder_info)
269
tmpname = '%s/broken.%s.tmp' % (self.path, rand_chars(20))
270
self.transport.rename(self._held_dir, tmpname)
231
tmpname = '%s.broken.%s.tmp' % (self.path, rand_chars(20))
232
self.transport.rename(self.path, tmpname)
271
233
# check that we actually broke the right lock, not someone else;
272
234
# there's a small race window between checking it and doing the
367
320
raise LockContention(self)
369
def lock_write(self):
370
"""Wait for and acquire the lock."""
374
"""Compatibility-mode shared lock.
376
LockDir doesn't support shared read-only locks, so this
377
just pretends that the lock is taken but really does nothing.
379
# At the moment Branches are commonly locked for read, but
380
# we can't rely on that remotely. Once this is cleaned up,
381
# reenable this warning to prevent it coming back in
383
## warn("LockDir.lock_read falls back to write lock")
384
if self._lock_held or self._fake_read_lock:
385
raise LockContention(self)
386
self._fake_read_lock = True
388
322
def wait(self, timeout=20, poll=0.5):
389
323
"""Wait a certain period for a lock to be released."""
390
324
# XXX: the transport interface doesn't let us guard