218
212
self.assertEqual([], self._logged_reports)
214
def test_32_lock_wait_succeed(self):
215
"""Succeed when trying to acquire a lock that gets released
217
One thread holds on a lock and then releases it; another
220
t = self.get_transport()
221
lf1 = LockDir(t, 'test_lock')
225
def wait_and_unlock():
228
unlocker = Thread(target=wait_and_unlock)
231
lf2 = LockDir(t, 'test_lock')
232
self.setup_log_reporter(lf2)
235
lf2.wait_lock(timeout=0.4, poll=0.1)
237
self.assertTrue(after - before <= 1.0)
241
# There should be only 1 report, even though it should have to
243
lock_base = lf2.transport.abspath(lf2.path)
244
self.assertEqual(1, len(self._logged_reports))
245
self.assertEqual('%s %s\n'
247
'Will continue to try until %s\n',
248
self._logged_reports[0][0])
249
args = self._logged_reports[0][1]
250
self.assertEqual('Unable to obtain', args[0])
251
self.assertEqual('lock %s' % (lock_base,), args[1])
252
self.assertStartsWith(args[2], 'held by ')
253
self.assertStartsWith(args[3], 'locked ')
254
self.assertEndsWith(args[3], ' ago')
255
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
257
def test_33_wait(self):
258
"""Succeed when waiting on a lock that gets released
260
The difference from test_32_lock_wait_succeed is that the second
261
caller does not actually acquire the lock, but just waits for it
262
to be released. This is done over a readonly transport.
264
t = self.get_transport()
265
lf1 = LockDir(t, 'test_lock')
269
def wait_and_unlock():
272
unlocker = Thread(target=wait_and_unlock)
275
lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
277
# wait but don't lock
278
lf2.wait(timeout=0.4, poll=0.1)
280
self.assertTrue(after - before <= 1.0)
284
def test_34_lock_write_waits(self):
285
"""LockDir.lock_write() will wait for the lock."""
286
t = self.get_transport()
287
lf1 = LockDir(t, 'test_lock')
291
def wait_and_unlock():
294
unlocker = Thread(target=wait_and_unlock)
297
lf2 = LockDir(t, 'test_lock')
298
self.setup_log_reporter(lf2)
306
# There should be only 1 report, even though it should have to
308
lock_base = lf2.transport.abspath(lf2.path)
309
self.assertEqual(1, len(self._logged_reports))
310
self.assertEqual('%s %s\n'
312
'Will continue to try until %s\n',
313
self._logged_reports[0][0])
314
args = self._logged_reports[0][1]
315
self.assertEqual('Unable to obtain', args[0])
316
self.assertEqual('lock %s' % (lock_base,), args[1])
317
self.assertStartsWith(args[2], 'held by ')
318
self.assertStartsWith(args[3], 'locked ')
319
self.assertEndsWith(args[3], ' ago')
320
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
322
def test_35_wait_lock_changing(self):
323
"""LockDir.wait_lock() will report if the lock changes underneath.
325
This is the stages we want to happen:
327
0) Synchronization locks are created and locked.
328
1) Lock1 obtains the lockdir, and releases the 'check' lock.
329
2) Lock2 grabs the 'check' lock, and checks the lockdir.
330
It sees the lockdir is already acquired, reports the fact,
331
and unsets the 'checked' lock.
332
3) Thread1 blocks on acquiring the 'checked' lock, and then tells
333
Lock1 to release and acquire the lockdir. This resets the 'check'
335
4) Lock2 acquires the 'check' lock, and checks again. It notices
336
that the holder of the lock has changed, and so reports a new
338
5) Thread1 blocks on the 'checked' lock, this time, it completely
339
unlocks the lockdir, allowing Lock2 to acquire the lock.
342
wait_to_check_lock = Lock()
343
wait_until_checked_lock = Lock()
345
wait_to_check_lock.acquire()
346
wait_until_checked_lock.acquire()
347
note('locked check and checked locks')
349
class LockDir1(LockDir):
350
"""Use the synchronization points for the first lock."""
352
def attempt_lock(self):
353
# Once we have acquired the lock, it is okay for
354
# the other lock to check it
356
return super(LockDir1, self).attempt_lock()
358
note('lock1: releasing check lock')
359
wait_to_check_lock.release()
361
class LockDir2(LockDir):
362
"""Use the synchronization points for the second lock."""
364
def attempt_lock(self):
365
note('lock2: waiting for check lock')
366
wait_to_check_lock.acquire()
367
note('lock2: acquired check lock')
369
return super(LockDir2, self).attempt_lock()
371
note('lock2: releasing checked lock')
372
wait_until_checked_lock.release()
374
t = self.get_transport()
375
lf1 = LockDir1(t, 'test_lock')
378
lf2 = LockDir2(t, 'test_lock')
379
self.setup_log_reporter(lf2)
381
def wait_and_switch():
383
# Block until lock2 has had a chance to check
384
note('lock1: waiting 1 for checked lock')
385
wait_until_checked_lock.acquire()
386
note('lock1: acquired for checked lock')
387
note('lock1: released lockdir')
389
note('lock1: acquiring lockdir')
390
# Create a new nonce, so the lock looks different.
391
lf1.nonce = osutils.rand_chars(20)
393
note('lock1: acquired lockdir')
395
# Block until lock2 has peeked again
396
note('lock1: waiting 2 for checked lock')
397
wait_until_checked_lock.acquire()
398
note('lock1: acquired for checked lock')
399
# Now unlock, and let lock 2 grab the lock
401
wait_to_check_lock.release()
403
unlocker = Thread(target=wait_and_switch)
406
# Wait and play against the other thread
407
lf2.wait_lock(timeout=1.0, poll=0.01)
412
# There should be 2 reports, because the lock changed
413
lock_base = lf2.transport.abspath(lf2.path)
414
self.assertEqual(2, len(self._logged_reports))
416
self.assertEqual('%s %s\n'
418
'Will continue to try until %s\n',
419
self._logged_reports[0][0])
420
args = self._logged_reports[0][1]
421
self.assertEqual('Unable to obtain', args[0])
422
self.assertEqual('lock %s' % (lock_base,), args[1])
423
self.assertStartsWith(args[2], 'held by ')
424
self.assertStartsWith(args[3], 'locked ')
425
self.assertEndsWith(args[3], ' ago')
426
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
428
self.assertEqual('%s %s\n'
430
'Will continue to try until %s\n',
431
self._logged_reports[1][0])
432
args = self._logged_reports[1][1]
433
self.assertEqual('Lock owner changed for', args[0])
434
self.assertEqual('lock %s' % (lock_base,), args[1])
435
self.assertStartsWith(args[2], 'held by ')
436
self.assertStartsWith(args[3], 'locked ')
437
self.assertEndsWith(args[3], ' ago')
438
self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
220
440
def test_40_confirm_easy(self):
221
441
"""Confirm a lock that's already held"""
222
442
t = self.get_transport()
223
443
lf1 = LockDir(t, 'test_lock')
225
445
lf1.attempt_lock()
226
self.addCleanup(lf1.unlock)
229
448
def test_41_confirm_not_held(self):
407
565
lf1 = LockDir(t, 'test_lock')
410
self.assertTrue(t.has('test_lock'))
568
self.failUnless(t.has('test_lock'))
412
570
t.rmdir('test_lock')
413
self.assertFalse(t.has('test_lock'))
571
self.failIf(t.has('test_lock'))
415
573
# This will create 'test_lock' if it needs to
417
self.assertTrue(t.has('test_lock'))
418
self.assertTrue(t.has('test_lock/held/info'))
575
self.failUnless(t.has('test_lock'))
576
self.failUnless(t.has('test_lock/held/info'))
421
self.assertFalse(t.has('test_lock/held/info'))
579
self.failIf(t.has('test_lock/held/info'))
423
def test_display_form(self):
581
def test__format_lock_info(self):
424
582
ld1 = self.get_lock()
428
info_list = ld1.peek().to_readable_dict()
586
info_list = ld1._format_lock_info(ld1.peek())
431
self.assertEqual(info_list['user'], u'jrandom@example.com')
432
self.assertContainsRe(info_list['pid'], '^\d+$')
433
self.assertContainsRe(info_list['time_ago'], r'^\d+ seconds? ago$')
589
self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
591
self.assertContainsRe(info_list[1],
592
r'^held by .* on host .* \[process #\d*\]$')
593
self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')
435
595
def test_lock_without_email(self):
436
global_config = config.GlobalStack()
596
global_config = config.GlobalConfig()
437
597
# Intentionally has no email address
438
global_config.set('email', 'User Identity')
598
global_config.set_user_option('email', 'User Identity')
439
599
ld1 = self.get_lock()
444
def test_lock_permission(self):
445
self.requireFeature(features.not_running_as_root)
446
if not osutils.supports_posix_readonly():
447
raise tests.TestSkipped('Cannot induce a permission failure')
448
ld1 = self.get_lock()
449
lock_path = ld1.transport.local_abspath('test_lock')
451
osutils.make_readonly(lock_path)
452
self.assertRaises(errors.LockFailed, ld1.attempt_lock)
454
def test_lock_by_token(self):
455
ld1 = self.get_lock()
456
token = ld1.lock_write()
457
self.addCleanup(ld1.unlock)
458
self.assertNotEqual(None, token)
459
ld2 = self.get_lock()
460
t2 = ld2.lock_write(token)
461
self.addCleanup(ld2.unlock)
462
self.assertEqual(token, t2)
464
def test_lock_with_buggy_rename(self):
465
# test that lock acquisition handles servers which pretend they
466
# renamed correctly but that actually fail
467
t = transport.get_transport_from_url(
468
'brokenrename+' + self.get_url())
469
ld1 = LockDir(t, 'test_lock')
472
ld2 = LockDir(t, 'test_lock')
473
# we should fail to lock
474
e = self.assertRaises(errors.LockContention, ld2.attempt_lock)
475
# now the original caller should succeed in unlocking
477
# and there should be nothing left over
478
self.assertEqual([], t.list_dir('test_lock'))
480
def test_failed_lock_leaves_no_trash(self):
481
# if we fail to acquire the lock, we don't leave pending directories
482
# behind -- https://bugs.launchpad.net/bzr/+bug/109169
483
ld1 = self.get_lock()
484
ld2 = self.get_lock()
485
# should be nothing before we start
487
t = self.get_transport().clone('test_lock')
490
self.assertEqual(a, t.list_dir('.'))
493
# when held, that's all we see
495
self.addCleanup(ld1.unlock)
497
# second guy should fail
498
self.assertRaises(errors.LockContention, ld2.attempt_lock)
502
def test_no_lockdir_info(self):
503
"""We can cope with empty info files."""
504
# This seems like a fairly common failure case - see
505
# <https://bugs.launchpad.net/bzr/+bug/185103> and all its dupes.
506
# Processes are often interrupted after opening the file
507
# before the actual contents are committed.
508
t = self.get_transport()
510
t.mkdir('test_lock/held')
511
t.put_bytes('test_lock/held/info', '')
512
lf = LockDir(t, 'test_lock')
514
formatted_info = info.to_readable_dict()
516
dict(user='<unknown>', hostname='<unknown>', pid='<unknown>',
517
time_ago='(unknown)'),
520
def test_corrupt_lockdir_info(self):
521
"""We can cope with corrupt (and thus unparseable) info files."""
522
# This seems like a fairly common failure case too - see
523
# <https://bugs.launchpad.net/bzr/+bug/619872> for instance.
524
# In particular some systems tend to fill recently created files with
525
# nul bytes after recovering from a system crash.
526
t = self.get_transport()
528
t.mkdir('test_lock/held')
529
t.put_bytes('test_lock/held/info', '\0')
530
lf = LockDir(t, 'test_lock')
531
self.assertRaises(errors.LockCorrupt, lf.peek)
532
# Currently attempt_lock gives LockContention, but LockCorrupt would be
533
# a reasonable result too.
535
(errors.LockCorrupt, errors.LockContention), lf.attempt_lock)
536
self.assertRaises(errors.LockCorrupt, lf.validate_token, 'fake token')
538
def test_missing_lockdir_info(self):
539
"""We can cope with absent info files."""
540
t = self.get_transport()
542
t.mkdir('test_lock/held')
543
lf = LockDir(t, 'test_lock')
544
# In this case we expect the 'not held' result from peek, because peek
545
# cannot be expected to notice that there is a 'held' directory with no
547
self.assertEqual(None, lf.peek())
548
# And lock/unlock may work or give LockContention (but not any other
552
except LockContention:
553
# LockContention is ok, and expected on Windows
556
# no error is ok, and expected on POSIX (because POSIX allows
557
# os.rename over an empty directory).
559
# Currently raises TokenMismatch, but LockCorrupt would be reasonable
562
(errors.TokenMismatch, errors.LockCorrupt),
563
lf.validate_token, 'fake token')
566
class TestLockDirHooks(TestCaseWithTransport):
569
super(TestLockDirHooks, self).setUp()
573
return LockDir(self.get_transport(), 'test_lock')
575
def record_hook(self, result):
576
self._calls.append(result)
578
def test_LockDir_acquired_success(self):
579
# the LockDir.lock_acquired hook fires when a lock is acquired.
580
LockDir.hooks.install_named_hook('lock_acquired',
581
self.record_hook, 'record_hook')
584
self.assertEqual([], self._calls)
585
result = ld.attempt_lock()
586
lock_path = ld.transport.abspath(ld.path)
587
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
589
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
591
def test_LockDir_acquired_fail(self):
592
# the LockDir.lock_acquired hook does not fire on failure.
595
ld2 = self.get_lock()
597
# install a lock hook now, when the disk lock is locked
598
LockDir.hooks.install_named_hook('lock_acquired',
599
self.record_hook, 'record_hook')
600
self.assertRaises(errors.LockContention, ld.attempt_lock)
601
self.assertEqual([], self._calls)
603
self.assertEqual([], self._calls)
605
def test_LockDir_released_success(self):
606
# the LockDir.lock_released hook fires when a lock is acquired.
607
LockDir.hooks.install_named_hook('lock_released',
608
self.record_hook, 'record_hook')
611
self.assertEqual([], self._calls)
612
result = ld.attempt_lock()
613
self.assertEqual([], self._calls)
615
lock_path = ld.transport.abspath(ld.path)
616
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
618
def test_LockDir_released_fail(self):
619
# the LockDir.lock_released hook does not fire on failure.
622
ld2 = self.get_lock()
624
ld2.force_break(ld2.peek())
625
LockDir.hooks.install_named_hook('lock_released',
626
self.record_hook, 'record_hook')
627
self.assertRaises(LockBroken, ld.unlock)
628
self.assertEqual([], self._calls)
630
def test_LockDir_broken_success(self):
631
# the LockDir.lock_broken hook fires when a lock is broken.
634
ld2 = self.get_lock()
635
result = ld.attempt_lock()
636
LockDir.hooks.install_named_hook('lock_broken',
637
self.record_hook, 'record_hook')
638
ld2.force_break(ld2.peek())
639
lock_path = ld.transport.abspath(ld.path)
640
self.assertEqual([lock.LockResult(lock_path, result)], self._calls)
642
def test_LockDir_broken_failure(self):
643
# the LockDir.lock_broken hook does not fires when a lock is already
647
ld2 = self.get_lock()
648
result = ld.attempt_lock()
649
holder_info = ld2.peek()
651
LockDir.hooks.install_named_hook('lock_broken',
652
self.record_hook, 'record_hook')
653
ld2.force_break(holder_info)
654
lock_path = ld.transport.abspath(ld.path)
655
self.assertEqual([], self._calls)
658
class TestLockHeldInfo(TestCaseInTempDir):
659
"""Can get information about the lock holder, and detect whether they're
663
info = LockHeldInfo.for_this_process(None)
664
self.assertContainsRe(repr(info), r"LockHeldInfo\(.*\)")
666
def test_unicode(self):
667
info = LockHeldInfo.for_this_process(None)
668
self.assertContainsRe(unicode(info),
669
r'held by .* on .* \(process #\d+\), acquired .* ago')
671
def test_is_locked_by_this_process(self):
672
info = LockHeldInfo.for_this_process(None)
673
self.assertTrue(info.is_locked_by_this_process())
675
def test_is_not_locked_by_this_process(self):
676
info = LockHeldInfo.for_this_process(None)
677
info.info_dict['pid'] = '123123123123123'
678
self.assertFalse(info.is_locked_by_this_process())
680
def test_lock_holder_live_process(self):
681
"""Detect that the holder (this process) is still running."""
682
info = LockHeldInfo.for_this_process(None)
683
self.assertFalse(info.is_lock_holder_known_dead())
685
def test_lock_holder_dead_process(self):
686
"""Detect that the holder (this process) is still running."""
687
self.overrideAttr(lockdir, 'get_host_name',
688
lambda: 'aproperhostname')
689
info = LockHeldInfo.for_this_process(None)
690
info.info_dict['pid'] = '123123123'
691
self.assertTrue(info.is_lock_holder_known_dead())
693
def test_lock_holder_other_machine(self):
694
"""The lock holder isn't here so we don't know if they're alive."""
695
info = LockHeldInfo.for_this_process(None)
696
info.info_dict['hostname'] = 'egg.example.com'
697
info.info_dict['pid'] = '123123123'
698
self.assertFalse(info.is_lock_holder_known_dead())
700
def test_lock_holder_other_user(self):
701
"""Only auto-break locks held by this user."""
702
info = LockHeldInfo.for_this_process(None)
703
info.info_dict['user'] = 'notme@example.com'
704
info.info_dict['pid'] = '123123123'
705
self.assertFalse(info.is_lock_holder_known_dead())
707
def test_no_good_hostname(self):
708
"""Correctly handle ambiguous hostnames.
710
If the lock's recorded with just 'localhost' we can't really trust
711
it's the same 'localhost'. (There are quite a few of them. :-)
712
So even if the process is known not to be alive, we can't say that's
715
self.overrideAttr(lockdir, 'get_host_name',
717
info = LockHeldInfo.for_this_process(None)
718
info.info_dict['pid'] = '123123123'
719
self.assertFalse(info.is_lock_holder_known_dead())
722
class TestStaleLockDir(TestCaseWithTransport):
723
"""Can automatically break stale locks.
725
:see: https://bugs.launchpad.net/bzr/+bug/220464
728
def test_auto_break_stale_lock(self):
729
"""Locks safely known to be stale are just cleaned up.
731
This generates a warning but no other user interaction.
733
self.overrideAttr(lockdir, 'get_host_name',
734
lambda: 'aproperhostname')
735
# This is off by default at present; see the discussion in the bug.
736
# If you change the default, don't forget to update the docs.
737
config.GlobalStack().set('locks.steal_dead', True)
738
# Create a lock pretending to come from a different nonexistent
739
# process on the same machine.
740
l1 = LockDir(self.get_transport(), 'a',
741
extra_holder_info={'pid': '12312313'})
742
token_1 = l1.attempt_lock()
743
l2 = LockDir(self.get_transport(), 'a')
744
token_2 = l2.attempt_lock()
745
# l1 will notice its lock was stolen.
746
self.assertRaises(errors.LockBroken,
750
def test_auto_break_stale_lock_configured_off(self):
751
"""Automatic breaking can be turned off"""
752
l1 = LockDir(self.get_transport(), 'a',
753
extra_holder_info={'pid': '12312313'})
754
token_1 = l1.attempt_lock()
755
self.addCleanup(l1.unlock)
756
l2 = LockDir(self.get_transport(), 'a')
757
# This fails now, because dead lock breaking is off by default.
758
self.assertRaises(LockContention,
760
# and it's in fact not broken