|
4 | 4 | // (found in the LICENSE.Apache file in the root directory).
|
5 | 5 |
|
6 | 6 | #include <atomic>
|
| 7 | +#include <cstdint> |
7 | 8 | #include <fstream>
|
8 | 9 | #include <memory>
|
9 | 10 | #include <thread>
|
@@ -605,23 +606,124 @@ TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) {
|
605 | 606 | Close();
|
606 | 607 | }
|
607 | 608 |
|
608 |
| -// Test that db->LockWAL() flushes the WAL after locking. |
609 |
| -TEST_P(DBWriteTest, LockWalInEffect) { |
| 609 | +// Test that db->LockWAL() flushes the WAL after locking, which can fail |
| 610 | +TEST_P(DBWriteTest, LockWALInEffect) { |
610 | 611 | Options options = GetOptions();
|
| 612 | + std::unique_ptr<FaultInjectionTestEnv> mock_env( |
| 613 | + new FaultInjectionTestEnv(env_)); |
| 614 | + options.env = mock_env.get(); |
| 615 | + options.paranoid_checks = false; |
611 | 616 | Reopen(options);
|
612 | 617 | // try the 1st WAL created during open
|
613 |
| - ASSERT_OK(Put("key" + std::to_string(0), "value")); |
614 |
| - ASSERT_TRUE(options.manual_wal_flush != dbfull()->WALBufferIsEmpty()); |
615 |
| - ASSERT_OK(dbfull()->LockWAL()); |
616 |
| - ASSERT_TRUE(dbfull()->WALBufferIsEmpty(false)); |
617 |
| - ASSERT_OK(dbfull()->UnlockWAL()); |
| 618 | + ASSERT_OK(Put("key0", "value")); |
| 619 | + ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty()); |
| 620 | + ASSERT_OK(db_->LockWAL()); |
| 621 | + ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); |
| 622 | + ASSERT_OK(db_->UnlockWAL()); |
618 | 623 | // try the 2nd wal created during SwitchWAL
|
619 | 624 | ASSERT_OK(dbfull()->TEST_SwitchWAL());
|
620 |
| - ASSERT_OK(Put("key" + std::to_string(0), "value")); |
621 |
| - ASSERT_TRUE(options.manual_wal_flush != dbfull()->WALBufferIsEmpty()); |
622 |
| - ASSERT_OK(dbfull()->LockWAL()); |
623 |
| - ASSERT_TRUE(dbfull()->WALBufferIsEmpty(false)); |
624 |
| - ASSERT_OK(dbfull()->UnlockWAL()); |
| 625 | + ASSERT_OK(Put("key1", "value")); |
| 626 | + ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty()); |
| 627 | + ASSERT_OK(db_->LockWAL()); |
| 628 | + ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); |
| 629 | + ASSERT_OK(db_->UnlockWAL()); |
| 630 | + |
| 631 | + // Fail the WAL flush if applicable |
| 632 | + mock_env->SetFilesystemActive(false); |
| 633 | + Status s = Put("key2", "value"); |
| 634 | + if (options.manual_wal_flush) { |
| 635 | + ASSERT_OK(s); |
| 636 | + // I/O failure |
| 637 | + ASSERT_NOK(db_->LockWAL()); |
| 638 | + // Should not need UnlockWAL after LockWAL fails |
| 639 | + } else { |
| 640 | + ASSERT_NOK(s); |
| 641 | + ASSERT_OK(db_->LockWAL()); |
| 642 | + ASSERT_OK(db_->UnlockWAL()); |
| 643 | + } |
| 644 | + mock_env->SetFilesystemActive(true); |
| 645 | + // Writes should work again |
| 646 | + ASSERT_OK(Put("key3", "value")); |
| 647 | + ASSERT_EQ(Get("key3"), "value"); |
| 648 | + |
| 649 | + // Should be extraneous, but allowed |
| 650 | + ASSERT_NOK(db_->UnlockWAL()); |
| 651 | + |
| 652 | + // Close before mock_env destruct. |
| 653 | + Close(); |
| 654 | +} |
| 655 | + |
| 656 | +TEST_P(DBWriteTest, LockWALConcurrentRecursive) { |
| 657 | + Options options = GetOptions(); |
| 658 | + Reopen(options); |
| 659 | + ASSERT_OK(Put("k1", "val")); |
| 660 | + ASSERT_OK(db_->LockWAL()); // 0 -> 1 |
| 661 | + auto frozen_seqno = db_->GetLatestSequenceNumber(); |
| 662 | + std::atomic<bool> t1_completed{false}; |
| 663 | + port::Thread t1{[&]() { |
| 664 | + // Won't finish until WAL unlocked |
| 665 | + ASSERT_OK(Put("k1", "val2")); |
| 666 | + t1_completed = true; |
| 667 | + }}; |
| 668 | + |
| 669 | + ASSERT_OK(db_->LockWAL()); // 1 -> 2 |
| 670 | + // Read-only ops are OK |
| 671 | + ASSERT_EQ(Get("k1"), "val"); |
| 672 | + { |
| 673 | + std::vector<LiveFileStorageInfo> files; |
| 674 | + LiveFilesStorageInfoOptions lf_opts; |
| 675 | + // A DB flush could deadlock |
| 676 | + lf_opts.wal_size_for_flush = UINT64_MAX; |
| 677 | + ASSERT_OK(db_->GetLiveFilesStorageInfo({lf_opts}, &files)); |
| 678 | + } |
| 679 | + |
| 680 | + port::Thread t2{[&]() { |
| 681 | + ASSERT_OK(db_->LockWAL()); // 2 -> 3 or 1 -> 2 |
| 682 | + }}; |
| 683 | + |
| 684 | + ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 or 3 -> 2 |
| 685 | + // Give t1 an extra chance to jump in case of bug |
| 686 | + std::this_thread::yield(); |
| 687 | + t2.join(); |
| 688 | + ASSERT_FALSE(t1_completed.load()); |
| 689 | + |
| 690 | + // Should now have 2 outstanding LockWAL |
| 691 | + ASSERT_EQ(Get("k1"), "val"); |
| 692 | + |
| 693 | + ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 |
| 694 | + |
| 695 | + ASSERT_FALSE(t1_completed.load()); |
| 696 | + ASSERT_EQ(Get("k1"), "val"); |
| 697 | + ASSERT_EQ(frozen_seqno, db_->GetLatestSequenceNumber()); |
| 698 | + |
| 699 | + // Ensure final Unlock is concurrency safe and extra Unlock is safe but |
| 700 | + // non-OK |
| 701 | + std::atomic<int> unlock_ok{0}; |
| 702 | + port::Thread t3{[&]() { |
| 703 | + if (db_->UnlockWAL().ok()) { |
| 704 | + unlock_ok++; |
| 705 | + } |
| 706 | + ASSERT_OK(db_->LockWAL()); |
| 707 | + if (db_->UnlockWAL().ok()) { |
| 708 | + unlock_ok++; |
| 709 | + } |
| 710 | + }}; |
| 711 | + |
| 712 | + if (db_->UnlockWAL().ok()) { |
| 713 | + unlock_ok++; |
| 714 | + } |
| 715 | + t3.join(); |
| 716 | + |
| 717 | + // There was one extra unlock, so just one non-ok |
| 718 | + ASSERT_EQ(unlock_ok.load(), 2); |
| 719 | + |
| 720 | + // Write can proceed |
| 721 | + t1.join(); |
| 722 | + ASSERT_TRUE(t1_completed.load()); |
| 723 | + ASSERT_EQ(Get("k1"), "val2"); |
| 724 | + // And new writes |
| 725 | + ASSERT_OK(Put("k2", "val")); |
| 726 | + ASSERT_EQ(Get("k2"), "val"); |
625 | 727 | }
|
626 | 728 |
|
627 | 729 | TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) {
|
|
0 commit comments