212
212
:raises LockContention: If the lock is held by someone else. The exception
213
213
contains the info of the current holder of the lock.
215
self._trace("lock_write...")
216
start_time = time.time()
217
tmpname = self._create_pending_dir()
216
self._trace("lock_write...")
217
start_time = time.time()
218
tmpname = self._create_pending_dir()
220
219
self.transport.rename(tmpname, self._held_dir)
221
# We must check we really got the lock, because Launchpad's sftp
222
# server at one time had a bug were the rename would successfully
223
# move the new directory into the existing directory, which was
224
# incorrect. It's possible some other servers or filesystems will
225
# have a similar bug allowing someone to think they got the lock
226
# when it's already held.
228
self._trace("after locking, info=%r", info)
229
if info['nonce'] != self.nonce:
230
self._trace("rename succeeded, "
231
"but lock is still held by someone else")
232
raise LockContention(self)
233
# we don't call confirm here because we don't want to set
234
# _lock_held til we're sure it's true, and because it's really a
235
# problem, not just regular contention, if this fails
236
self._lock_held = True
237
# FIXME: we should remove the pending lock if we fail,
238
# https://bugs.launchpad.net/bzr/+bug/109169
239
except errors.PermissionDenied:
240
self._trace("... lock failed, permission denied")
242
220
except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
243
221
self._trace("... contention, %s", e)
244
raise LockContention(self)
222
self._remove_pending_dir(tmpname)
223
raise LockContention(self)
225
self._trace("... lock failed, %s", e)
226
self._remove_pending_dir(tmpname)
228
# We must check we really got the lock, because Launchpad's sftp
229
# server at one time had a bug were the rename would successfully
230
# move the new directory into the existing directory, which was
231
# incorrect. It's possible some other servers or filesystems will
232
# have a similar bug allowing someone to think they got the lock
233
# when it's already held.
235
self._trace("after locking, info=%r", info)
236
if info['nonce'] != self.nonce:
237
self._trace("rename succeeded, "
238
"but lock is still held by someone else")
239
raise LockContention(self)
240
# we don't call confirm here because we don't want to set
241
# _lock_held til we're sure it's true, and because it's really a
242
# problem, not just regular contention, if this fails
243
self._lock_held = True
244
# FIXME: we should remove the pending lock if we fail,
245
# https://bugs.launchpad.net/bzr/+bug/109169
245
246
self._trace("... lock succeeded after %dms",
246
247
(time.time() - start_time) * 1000)
247
248
return self.nonce
250
def _remove_pending_dir(self, tmpname):
251
"""Remove the pending directory
253
This is called if we failed to rename into place, so that the pending
254
dirs don't clutter up the lockdir.
256
self._trace("remove %s", tmpname)
257
self.transport.delete(tmpname + self.__INFO_NAME)
258
self.transport.rmdir(tmpname)
249
260
def _create_pending_dir(self):
250
261
tmpname = '%s/%s.tmp' % (self.path, rand_chars(10))
466
481
deadline_str = None
468
483
attempt_count = 0
469
if self._fake_read_lock:
470
raise LockContention(self)
471
if self.transport.is_readonly():
472
raise UnlockableTransport(self.transport)
474
485
attempt_count += 1
476
return self._lock_core()
477
except LockContention, err:
478
# TODO: LockContention should only be raised when we're know
479
# that the lock is held by someone else, in which case we
480
# should include the locker info, so it can be used here.
481
# In other cases, such as having a malformed lock present, we
482
# should raise a different.
484
# we shouldn't need to peek again here, because _lock_core
486
new_info = self.peek()
487
if new_info is not None and new_info != last_info:
488
if last_info is None:
489
start = 'Unable to obtain'
491
start = 'Lock owner changed for'
493
formatted_info = self._format_lock_info(new_info)
494
if deadline_str is None:
495
deadline_str = time.strftime('%H:%M:%S',
496
time.localtime(deadline))
497
self._report_function('%s %s\n'
499
'%s\n' # locked ... ago
500
'Will continue to try until %s\n',
507
if (max_attempts is not None) and (attempt_count >= max_attempts):
508
self._trace("exceeded %d attempts")
509
raise LockContention(self)
510
if time.time() + poll < deadline:
511
self._trace("waiting %ss", poll)
487
return self.attempt_lock()
488
except LockContention:
489
# possibly report the blockage, then try again
491
# TODO: In a few cases, we find out that there's contention by
492
# reading the held info and observing that it's not ours. In
493
# those cases it's a bit redundant to read it again. However,
494
# the normal case (??) is that the rename fails and so we
495
# don't know who holds the lock. For simplicity we peek
497
new_info = self.peek()
498
if new_info is not None and new_info != last_info:
499
if last_info is None:
500
start = 'Unable to obtain'
514
self._trace("timeout after waiting %ss", timeout)
515
raise LockContention(self)
502
start = 'Lock owner changed for'
504
formatted_info = self._format_lock_info(new_info)
505
if deadline_str is None:
506
deadline_str = time.strftime('%H:%M:%S',
507
time.localtime(deadline))
508
self._report_function('%s %s\n'
510
'%s\n' # locked ... ago
511
'Will continue to try until %s\n',
518
if (max_attempts is not None) and (attempt_count >= max_attempts):
519
self._trace("exceeded %d attempts")
520
raise LockContention(self)
521
if time.time() + poll < deadline:
522
self._trace("waiting %ss", poll)
525
self._trace("timeout after waiting %ss", timeout)
526
raise LockContention(self)
517
528
def leave_in_place(self):
518
529
self._locked_via_token = True