115
112
UnlockableTransport,
117
from bzrlib.trace import mutter, note
114
from bzrlib.trace import mutter
118
115
from bzrlib.transport import Transport
119
from bzrlib.osutils import rand_chars, format_delta
116
from bzrlib.osutils import rand_chars
120
117
from bzrlib.rio import read_stanza, Stanza
124
119
# XXX: At the moment there is no consideration of thread safety on LockDir
125
120
# objects. This should perhaps be updated - e.g. if two threads try to take a
160
153
self.transport = transport
162
155
self._lock_held = False
163
self._locked_via_token = False
164
156
self._fake_read_lock = False
165
157
self._held_dir = path + '/held'
166
158
self._held_info_path = self._held_dir + self.__INFO_NAME
167
159
self._file_modebits = file_modebits
168
160
self._dir_modebits = dir_modebits
170
self._report_function = note
161
self.nonce = rand_chars(20)
172
163
def __repr__(self):
173
164
return '%s(%s%s)' % (self.__class__.__name__,
209
200
# After creating the lock directory, try again
210
201
self.transport.mkdir(tmpname)
212
self.nonce = rand_chars(20)
213
203
info_bytes = self._prepare_info()
214
204
# We use put_file_non_atomic because we just created a new unique
215
205
# directory so we don't have to worry about files existing there.
221
211
self.transport.rename(tmpname, self._held_dir)
222
212
self._lock_held = True
224
except errors.PermissionDenied:
226
214
except (PathError, DirectoryNotEmpty, FileExists, ResourceBusy), e:
227
215
mutter("contention on %r: %s", self, e)
228
216
raise LockContention(self)
236
224
if not self._lock_held:
237
225
raise LockNotHeld(self)
238
if self._locked_via_token:
239
self._locked_via_token = False
240
self._lock_held = False
242
# rename before deleting, because we can't atomically remove the
244
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
245
# gotta own it to unlock
247
self.transport.rename(self._held_dir, tmpname)
248
self._lock_held = False
249
self.transport.delete(tmpname + self.__INFO_NAME)
250
self.transport.rmdir(tmpname)
226
# rename before deleting, because we can't atomically remove the whole
228
tmpname = '%s/releasing.%s.tmp' % (self.path, rand_chars(20))
229
# gotta own it to unlock
231
self.transport.rename(self._held_dir, tmpname)
232
self._lock_held = False
233
self.transport.delete(tmpname + self.__INFO_NAME)
234
self.transport.rmdir(tmpname)
252
236
def break_lock(self):
253
237
"""Break a lock not held by this instance of LockDir.
259
243
self._check_not_locked()
260
244
holder_info = self.peek()
261
245
if holder_info is not None:
262
lock_info = '\n'.join(self._format_lock_info(holder_info))
263
if bzrlib.ui.ui_factory.get_boolean("Break %s" % lock_info):
246
if bzrlib.ui.ui_factory.get_boolean(
247
"Break lock %s held by %s@%s [process #%s]" % (
250
holder_info["hostname"],
251
holder_info["pid"])):
264
252
self.force_break(holder_info)
266
254
def force_break(self, dead_holder_info):
354
342
# XXX: is creating this here inefficient?
355
343
config = bzrlib.config.GlobalConfig()
357
user = config.user_email()
358
except errors.NoEmailInUsername:
359
user = config.username()
360
344
s = Stanza(hostname=socket.gethostname(),
361
345
pid=str(os.getpid()),
362
346
start_time=str(int(time.time())),
363
347
nonce=self.nonce,
348
user=config.user_email(),
366
350
return s.to_string()
368
352
def _parse_info(self, info_file):
369
353
return read_stanza(info_file.readlines()).as_dict()
371
def wait_lock(self, timeout=None, poll=None):
355
def wait_lock(self, timeout=_DEFAULT_TIMEOUT_SECONDS,
356
poll=_DEFAULT_POLL_SECONDS):
372
357
"""Wait a certain period for a lock.
374
359
If the lock can be acquired within the bounded time, it
377
362
approximately `timeout` seconds. (It may be a bit more if
378
363
a transport operation takes a long time to complete.)
381
timeout = _DEFAULT_TIMEOUT_SECONDS
383
poll = _DEFAULT_POLL_SECONDS
385
365
# XXX: the transport interface doesn't let us guard
386
366
# against operations there taking a long time.
387
367
deadline = time.time() + timeout
392
370
self.attempt_lock()
394
372
except LockContention:
396
new_info = self.peek()
397
mutter('last_info: %s, new info: %s', last_info, new_info)
398
if new_info is not None and new_info != last_info:
399
if last_info is None:
400
start = 'Unable to obtain'
402
start = 'Lock owner changed for'
404
formatted_info = self._format_lock_info(new_info)
405
if deadline_str is None:
406
deadline_str = time.strftime('%H:%M:%S',
407
time.localtime(deadline))
408
self._report_function('%s %s\n'
410
'%s\n' # locked ... ago
411
'Will continue to try until %s\n',
418
374
if time.time() + poll < deadline:
421
377
raise LockContention(self)
423
def leave_in_place(self):
424
self._locked_via_token = True
426
def dont_leave_in_place(self):
427
self._locked_via_token = False
429
def lock_write(self, token=None):
430
"""Wait for and acquire the lock.
432
:param token: if this is already locked, then lock_write will fail
433
unless the token matches the existing lock.
434
:returns: a token if this instance supports tokens, otherwise None.
435
:raises TokenLockingNotSupported: when a token is given but this
436
instance doesn't support using token locks.
437
:raises MismatchedToken: if the specified token doesn't match the token
438
of the existing lock.
440
A token should be passed in if you know that you have locked the object
441
some other way, and need to synchronise this object's state with that
444
XXX: docstring duplicated from LockableFiles.lock_write.
446
if token is not None:
447
self.validate_token(token)
449
self._lock_held = True
450
self._locked_via_token = True
454
return self.peek().get('nonce')
379
def lock_write(self):
380
"""Wait for and acquire the lock."""
456
383
def lock_read(self):
457
384
"""Compatibility-mode shared lock.
482
409
raise LockContention(self)
484
def _format_lock_info(self, info):
485
"""Turn the contents of peek() into something for the user"""
486
lock_url = self.transport.abspath(self.path)
487
delta = time.time() - int(info['start_time'])
489
'lock %s' % (lock_url,),
490
'held by %(user)s on host %(hostname)s [process #%(pid)s]' % info,
491
'locked %s' % (format_delta(delta),),
494
def validate_token(self, token):
495
if token is not None:
501
lock_token = info.get('nonce')
502
if token != lock_token:
503
raise errors.TokenMismatch(token, lock_token)