| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "peridot/bin/ledger/storage/impl/page_db.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/callback/set_when_called.h> |
| #include <lib/fxl/macros.h> |
| #include <lib/zx/time.h> |
| |
| #include "gtest/gtest.h" |
| #include "peridot/bin/ledger/encryption/fake/fake_encryption_service.h" |
| #include "peridot/bin/ledger/storage/impl/commit_impl.h" |
| #include "peridot/bin/ledger/storage/impl/commit_random_impl.h" |
| #include "peridot/bin/ledger/storage/impl/journal_impl.h" |
| #include "peridot/bin/ledger/storage/impl/leveldb.h" |
| #include "peridot/bin/ledger/storage/impl/page_db_impl.h" |
| #include "peridot/bin/ledger/storage/impl/page_storage_impl.h" |
| #include "peridot/bin/ledger/storage/impl/storage_test_utils.h" |
| #include "peridot/bin/ledger/storage/public/constants.h" |
| #include "peridot/bin/ledger/testing/test_with_environment.h" |
| #include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h" |
| |
| namespace storage { |
| namespace { |
| |
| using coroutine::CoroutineHandler; |
| |
| void ExpectChangesEqual(const EntryChange& expected, const EntryChange& found) { |
| EXPECT_EQ(expected.deleted, found.deleted); |
| EXPECT_EQ(expected.entry.key, found.entry.key); |
| if (!expected.deleted) { |
| // If the entry is deleted, object_identifier and priority are not valid. |
| EXPECT_EQ(expected.entry, found.entry); |
| } |
| } |
| |
| std::unique_ptr<LevelDb> GetLevelDb(async_dispatcher_t* dispatcher, |
| ledger::DetachedPath db_path) { |
| auto db = std::make_unique<LevelDb>(dispatcher, std::move(db_path)); |
| EXPECT_EQ(Status::OK, db->Init()); |
| return db; |
| } |
| |
| class PageDbTest : public ledger::TestWithEnvironment { |
| public: |
| PageDbTest() |
| : encryption_service_(dispatcher()), |
| base_path(tmpfs_.root_fd()), |
| page_storage_(&environment_, &encryption_service_, |
| GetLevelDb(dispatcher(), base_path.SubPath("storage")), |
| "page_id"), |
| page_db_(&environment_, |
| GetLevelDb(dispatcher(), base_path.SubPath("page_db"))) {} |
| |
| ~PageDbTest() override {} |
| |
| // Test: |
| void SetUp() override { |
| std::srand(0); |
| |
| Status status; |
| bool called; |
| page_storage_.Init( |
| callback::Capture(callback::SetWhenCalled(&called), &status)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(called); |
| ASSERT_EQ(Status::OK, status); |
| } |
| |
| protected: |
| scoped_tmpfs::ScopedTmpFS tmpfs_; |
| encryption::FakeEncryptionService encryption_service_; |
| ledger::DetachedPath base_path; |
| PageStorageImpl page_storage_; |
| PageDbImpl page_db_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(PageDbTest); |
| }; |
| |
| TEST_F(PageDbTest, HeadCommits) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| std::vector<CommitId> heads; |
| EXPECT_EQ(Status::OK, page_db_.GetHeads(handler, &heads)); |
| EXPECT_TRUE(heads.empty()); |
| |
| CommitId cid = RandomCommitId(environment_.random()); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddHead(handler, cid, |
| environment_.random()->Draw<zx::time_utc>())); |
| EXPECT_EQ(Status::OK, page_db_.GetHeads(handler, &heads)); |
| EXPECT_EQ(1u, heads.size()); |
| EXPECT_EQ(cid, heads[0]); |
| |
| EXPECT_EQ(Status::OK, page_db_.RemoveHead(handler, cid)); |
| EXPECT_EQ(Status::OK, page_db_.GetHeads(handler, &heads)); |
| EXPECT_TRUE(heads.empty()); |
| }); |
| } |
| |
| TEST_F(PageDbTest, OrderHeadCommitsByTimestamp) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| std::vector<zx::time_utc> timestamps = {zx::time_utc::infinite_past(), |
| zx::time_utc::infinite(), |
| zx::time_utc()}; |
| |
| for (size_t i = 0; i < 10; ++i) { |
| zx::time_utc ts; |
| do { |
| ts = environment_.random()->Draw<zx::time_utc>(); |
| } while (std::find(timestamps.begin(), timestamps.end(), ts) != |
| timestamps.end()); |
| timestamps.push_back(ts); |
| } |
| |
| auto sorted_timestamps = timestamps; |
| std::sort(sorted_timestamps.begin(), sorted_timestamps.end()); |
| auto random_ordered_timestamps = timestamps; |
| auto rng = environment_.random()->NewBitGenerator<uint64_t>(); |
| std::shuffle(random_ordered_timestamps.begin(), |
| random_ordered_timestamps.end(), rng); |
| |
| std::map<zx::time_utc, CommitId> commits; |
| for (auto ts : random_ordered_timestamps) { |
| commits[ts] = RandomCommitId(environment_.random()); |
| EXPECT_EQ(Status::OK, page_db_.AddHead(handler, commits[ts], ts)); |
| } |
| |
| std::vector<CommitId> heads; |
| EXPECT_EQ(Status::OK, page_db_.GetHeads(handler, &heads)); |
| EXPECT_EQ(timestamps.size(), heads.size()); |
| |
| for (size_t i = 0; i < heads.size(); ++i) { |
| EXPECT_EQ(commits[sorted_timestamps[i]], heads[i]); |
| } |
| }); |
| } |
| |
| TEST_F(PageDbTest, Commits) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| std::vector<std::unique_ptr<const Commit>> parents; |
| parents.emplace_back( |
| std::make_unique<CommitRandomImpl>(environment_.random())); |
| |
| std::unique_ptr<const Commit> commit = CommitImpl::FromContentAndParents( |
| environment_.clock(), &page_storage_, |
| RandomObjectIdentifier(environment_.random()), std::move(parents)); |
| |
| std::string storage_bytes; |
| EXPECT_EQ(Status::NOT_FOUND, page_db_.GetCommitStorageBytes( |
| handler, commit->GetId(), &storage_bytes)); |
| |
| EXPECT_EQ(Status::OK, |
| page_db_.AddCommitStorageBytes(handler, commit->GetId(), |
| commit->GetStorageBytes())); |
| EXPECT_EQ(Status::OK, page_db_.GetCommitStorageBytes( |
| handler, commit->GetId(), &storage_bytes)); |
| EXPECT_EQ(storage_bytes, commit->GetStorageBytes()); |
| |
| EXPECT_EQ(Status::OK, page_db_.RemoveCommit(handler, commit->GetId())); |
| EXPECT_EQ(Status::NOT_FOUND, page_db_.GetCommitStorageBytes( |
| handler, commit->GetId(), &storage_bytes)); |
| }); |
| } |
| |
| TEST_F(PageDbTest, Journals) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| CommitId commit_id = RandomCommitId(environment_.random()); |
| |
| JournalId implicit_journal_id; |
| JournalId explicit_journal_id; |
| std::unique_ptr<Journal> explicit_journal; |
| EXPECT_EQ(Status::OK, |
| page_db_.CreateJournalId(handler, JournalType::IMPLICIT, |
| commit_id, &implicit_journal_id)); |
| EXPECT_EQ(Status::OK, |
| page_db_.CreateJournalId(handler, JournalType::EXPLICIT, |
| commit_id, &explicit_journal_id)); |
| |
| EXPECT_EQ(Status::OK, page_db_.RemoveExplicitJournals(handler)); |
| |
| // Removing explicit journals should not affect the implicit ones. |
| std::vector<JournalId> journal_ids; |
| EXPECT_EQ(Status::OK, |
| page_db_.GetImplicitJournalIds(handler, &journal_ids)); |
| ASSERT_EQ(1u, journal_ids.size()); |
| EXPECT_EQ(implicit_journal_id, journal_ids[0]); |
| |
| CommitId found_base_id; |
| EXPECT_EQ(Status::OK, page_db_.GetBaseCommitForJournal( |
| handler, journal_ids[0], &found_base_id)); |
| EXPECT_EQ(commit_id, found_base_id); |
| EXPECT_EQ(Status::OK, page_db_.RemoveJournal(handler, journal_ids[0])); |
| EXPECT_EQ(Status::NOT_FOUND, page_db_.GetBaseCommitForJournal( |
| handler, journal_ids[0], &found_base_id)); |
| EXPECT_EQ(Status::OK, |
| page_db_.GetImplicitJournalIds(handler, &journal_ids)); |
| EXPECT_EQ(0u, journal_ids.size()); |
| }); |
| } |
| |
| TEST_F(PageDbTest, JournalEntries) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| CommitId commit_id = RandomCommitId(environment_.random()); |
| |
| JournalId journal_id; |
| EXPECT_EQ(Status::OK, |
| page_db_.CreateJournalId(handler, JournalType::IMPLICIT, |
| commit_id, &journal_id)); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddJournalEntry(handler, journal_id, "add-key-1", |
| MakeObjectIdentifier("value1"), |
| KeyPriority::LAZY)); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddJournalEntry(handler, journal_id, "add-key-2", |
| MakeObjectIdentifier("value2"), |
| KeyPriority::EAGER)); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddJournalEntry(handler, journal_id, "add-key-1", |
| MakeObjectIdentifier("value3"), |
| KeyPriority::LAZY)); |
| EXPECT_EQ(Status::OK, |
| page_db_.RemoveJournalEntry(handler, journal_id, "remove-key")); |
| |
| EntryChange expected_changes[] = { |
| NewEntryChange("add-key-1", "value3", KeyPriority::LAZY), |
| NewEntryChange("add-key-2", "value2", KeyPriority::EAGER), |
| NewRemoveEntryChange("remove-key"), |
| }; |
| std::unique_ptr<Iterator<const EntryChange>> entries; |
| JournalContainsClearOperation contains_clear_operation; |
| EXPECT_EQ(Status::OK, |
| page_db_.GetJournalEntries(handler, journal_id, &entries, |
| &contains_clear_operation)); |
| for (const auto& expected_change : expected_changes) { |
| EXPECT_TRUE(entries->Valid()); |
| ExpectChangesEqual(expected_change, **entries); |
| entries->Next(); |
| } |
| EXPECT_FALSE(entries->Valid()); |
| EXPECT_EQ(JournalContainsClearOperation::NO, contains_clear_operation); |
| EXPECT_EQ(Status::OK, entries->GetStatus()); |
| }); |
| } |
| |
| TEST_F(PageDbTest, JournalEntriesWithClear) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| CommitId commit_id = RandomCommitId(environment_.random()); |
| |
| JournalId journal_id; |
| EXPECT_EQ(Status::OK, |
| page_db_.CreateJournalId(handler, JournalType::IMPLICIT, |
| commit_id, &journal_id)); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddJournalEntry(handler, journal_id, "add-key-1", |
| MakeObjectIdentifier("value1"), |
| KeyPriority::LAZY)); |
| EXPECT_EQ(Status::OK, page_db_.EmptyJournalAndMarkContainsClearOperation( |
| handler, journal_id)); |
| EXPECT_EQ(Status::OK, |
| page_db_.AddJournalEntry(handler, journal_id, "add-key-2", |
| MakeObjectIdentifier("value2"), |
| KeyPriority::EAGER)); |
| EXPECT_EQ(Status::OK, |
| page_db_.RemoveJournalEntry(handler, journal_id, "remove-key")); |
| |
| EntryChange expected_changes[] = { |
| NewEntryChange("add-key-2", "value2", KeyPriority::EAGER), |
| NewRemoveEntryChange("remove-key"), |
| }; |
| std::unique_ptr<Iterator<const EntryChange>> entries; |
| JournalContainsClearOperation contains_clear_operation; |
| EXPECT_EQ(Status::OK, |
| page_db_.GetJournalEntries(handler, journal_id, &entries, |
| &contains_clear_operation)); |
| for (const auto& expected_change : expected_changes) { |
| EXPECT_TRUE(entries->Valid()); |
| ExpectChangesEqual(expected_change, **entries); |
| entries->Next(); |
| } |
| EXPECT_FALSE(entries->Valid()); |
| EXPECT_EQ(JournalContainsClearOperation::YES, contains_clear_operation); |
| EXPECT_EQ(Status::OK, entries->GetStatus()); |
| }); |
| } |
| |
| TEST_F(PageDbTest, ObjectStorage) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| ObjectIdentifier object_identifier = |
| RandomObjectIdentifier(environment_.random()); |
| std::string content = RandomString(environment_.random(), 32 * 1024); |
| std::unique_ptr<const Object> object; |
| PageDbObjectStatus object_status; |
| |
| EXPECT_EQ(Status::NOT_FOUND, |
| page_db_.ReadObject(handler, object_identifier, &object)); |
| ASSERT_EQ(Status::OK, |
| page_db_.WriteObject(handler, object_identifier, |
| DataSource::DataChunk::Create(content), |
| PageDbObjectStatus::TRANSIENT)); |
| page_db_.GetObjectStatus(handler, object_identifier, &object_status); |
| EXPECT_EQ(PageDbObjectStatus::TRANSIENT, object_status); |
| ASSERT_EQ(Status::OK, |
| page_db_.ReadObject(handler, object_identifier, &object)); |
| fxl::StringView object_content; |
| EXPECT_EQ(Status::OK, object->GetData(&object_content)); |
| EXPECT_EQ(content, object_content); |
| // Update the object to LOCAL. The new content should be ignored. |
| std::string new_content = RandomString(environment_.random(), 32 * 1024); |
| ASSERT_EQ(Status::OK, |
| page_db_.WriteObject(handler, object_identifier, |
| DataSource::DataChunk::Create(new_content), |
| PageDbObjectStatus::LOCAL)); |
| page_db_.GetObjectStatus(handler, object_identifier, &object_status); |
| EXPECT_EQ(PageDbObjectStatus::LOCAL, object_status); |
| EXPECT_EQ(Status::OK, object->GetData(&object_content)); |
| EXPECT_EQ(content, object_content); |
| EXPECT_NE(new_content, object_content); |
| }); |
| } |
| |
| TEST_F(PageDbTest, UnsyncedCommits) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| CommitId commit_id = RandomCommitId(environment_.random()); |
| std::vector<CommitId> commit_ids; |
| EXPECT_EQ(Status::OK, page_db_.GetUnsyncedCommitIds(handler, &commit_ids)); |
| EXPECT_TRUE(commit_ids.empty()); |
| |
| EXPECT_EQ(Status::OK, page_db_.MarkCommitIdUnsynced(handler, commit_id, 0)); |
| EXPECT_EQ(Status::OK, page_db_.GetUnsyncedCommitIds(handler, &commit_ids)); |
| EXPECT_EQ(1u, commit_ids.size()); |
| EXPECT_EQ(commit_id, commit_ids[0]); |
| bool is_synced; |
| EXPECT_EQ(Status::OK, |
| page_db_.IsCommitSynced(handler, commit_id, &is_synced)); |
| EXPECT_FALSE(is_synced); |
| |
| EXPECT_EQ(Status::OK, page_db_.MarkCommitIdSynced(handler, commit_id)); |
| EXPECT_EQ(Status::OK, page_db_.GetUnsyncedCommitIds(handler, &commit_ids)); |
| EXPECT_TRUE(commit_ids.empty()); |
| EXPECT_EQ(Status::OK, |
| page_db_.IsCommitSynced(handler, commit_id, &is_synced)); |
| EXPECT_TRUE(is_synced); |
| }); |
| } |
| |
| TEST_F(PageDbTest, OrderUnsyncedCommitsByTimestamp) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| CommitId commit_ids[] = {RandomCommitId(environment_.random()), |
| RandomCommitId(environment_.random()), |
| RandomCommitId(environment_.random())}; |
| // Add three unsynced commits with timestamps 200, 300 and 100. |
| EXPECT_EQ(Status::OK, |
| page_db_.MarkCommitIdUnsynced(handler, commit_ids[0], 200)); |
| EXPECT_EQ(Status::OK, |
| page_db_.MarkCommitIdUnsynced(handler, commit_ids[1], 300)); |
| EXPECT_EQ(Status::OK, |
| page_db_.MarkCommitIdUnsynced(handler, commit_ids[2], 100)); |
| |
| // The result should be ordered by the given timestamps. |
| std::vector<CommitId> found_ids; |
| EXPECT_EQ(Status::OK, page_db_.GetUnsyncedCommitIds(handler, &found_ids)); |
| EXPECT_EQ(3u, found_ids.size()); |
| EXPECT_EQ(found_ids[0], commit_ids[2]); |
| EXPECT_EQ(found_ids[1], commit_ids[0]); |
| EXPECT_EQ(found_ids[2], commit_ids[1]); |
| }); |
| } |
| |
| TEST_F(PageDbTest, UnsyncedPieces) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| auto object_identifier = RandomObjectIdentifier(environment_.random()); |
| std::vector<ObjectIdentifier> object_identifiers; |
| EXPECT_EQ(Status::OK, |
| page_db_.GetUnsyncedPieces(handler, &object_identifiers)); |
| EXPECT_TRUE(object_identifiers.empty()); |
| |
| EXPECT_EQ(Status::OK, |
| page_db_.WriteObject(handler, object_identifier, |
| DataSource::DataChunk::Create(""), |
| PageDbObjectStatus::LOCAL)); |
| EXPECT_EQ(Status::OK, page_db_.SetObjectStatus(handler, object_identifier, |
| PageDbObjectStatus::LOCAL)); |
| EXPECT_EQ(Status::OK, |
| page_db_.GetUnsyncedPieces(handler, &object_identifiers)); |
| EXPECT_EQ(1u, object_identifiers.size()); |
| EXPECT_EQ(object_identifier, object_identifiers[0]); |
| PageDbObjectStatus object_status; |
| EXPECT_EQ(Status::OK, page_db_.GetObjectStatus(handler, object_identifier, |
| &object_status)); |
| EXPECT_EQ(PageDbObjectStatus::LOCAL, object_status); |
| |
| EXPECT_EQ(Status::OK, page_db_.SetObjectStatus(handler, object_identifier, |
| PageDbObjectStatus::SYNCED)); |
| EXPECT_EQ(Status::OK, |
| page_db_.GetUnsyncedPieces(handler, &object_identifiers)); |
| EXPECT_TRUE(object_identifiers.empty()); |
| EXPECT_EQ(Status::OK, page_db_.GetObjectStatus(handler, object_identifier, |
| &object_status)); |
| EXPECT_EQ(PageDbObjectStatus::SYNCED, object_status); |
| }); |
| } |
| |
| TEST_F(PageDbTest, Batch) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| std::unique_ptr<PageDb::Batch> batch; |
| ASSERT_EQ(Status::OK, page_db_.StartBatch(handler, &batch)); |
| ASSERT_TRUE(batch); |
| |
| auto object_identifier = RandomObjectIdentifier(environment_.random()); |
| EXPECT_EQ(Status::OK, batch->WriteObject(handler, object_identifier, |
| DataSource::DataChunk::Create(""), |
| PageDbObjectStatus::LOCAL)); |
| |
| std::vector<ObjectIdentifier> object_identifiers; |
| EXPECT_EQ(Status::OK, |
| page_db_.GetUnsyncedPieces(handler, &object_identifiers)); |
| EXPECT_TRUE(object_identifiers.empty()); |
| |
| EXPECT_EQ(Status::OK, batch->Execute(handler)); |
| |
| EXPECT_EQ(Status::OK, |
| page_db_.GetUnsyncedPieces(handler, &object_identifiers)); |
| EXPECT_EQ(1u, object_identifiers.size()); |
| EXPECT_EQ(object_identifier, object_identifiers[0]); |
| }); |
| } |
| |
| TEST_F(PageDbTest, PageDbObjectStatus) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| PageDbObjectStatus initial_statuses[] = {PageDbObjectStatus::TRANSIENT, |
| PageDbObjectStatus::LOCAL, |
| PageDbObjectStatus::SYNCED}; |
| PageDbObjectStatus next_statuses[] = {PageDbObjectStatus::LOCAL, |
| PageDbObjectStatus::SYNCED}; |
| for (auto initial_status : initial_statuses) { |
| for (auto next_status : next_statuses) { |
| auto object_identifier = RandomObjectIdentifier(environment_.random()); |
| PageDbObjectStatus object_status; |
| ASSERT_EQ(Status::OK, page_db_.GetObjectStatus( |
| handler, object_identifier, &object_status)); |
| EXPECT_EQ(PageDbObjectStatus::UNKNOWN, object_status); |
| ASSERT_EQ(Status::OK, |
| page_db_.WriteObject(handler, object_identifier, |
| DataSource::DataChunk::Create(""), |
| initial_status)); |
| ASSERT_EQ(Status::OK, page_db_.GetObjectStatus( |
| handler, object_identifier, &object_status)); |
| EXPECT_EQ(initial_status, object_status); |
| ASSERT_EQ(Status::OK, page_db_.SetObjectStatus( |
| handler, object_identifier, next_status)); |
| |
| PageDbObjectStatus expected_status = |
| std::max(initial_status, next_status); |
| ASSERT_EQ(Status::OK, page_db_.GetObjectStatus( |
| handler, object_identifier, &object_status)); |
| EXPECT_EQ(expected_status, object_status); |
| } |
| } |
| }); |
| } |
| |
| TEST_F(PageDbTest, SyncMetadata) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| std::vector<std::pair<fxl::StringView, fxl::StringView>> keys_and_values = { |
| {"foo1", "foo2"}, {"bar1", " bar2 "}}; |
| for (const auto& key_and_value : keys_and_values) { |
| auto key = key_and_value.first; |
| auto value = key_and_value.second; |
| std::string returned_value; |
| EXPECT_EQ(Status::NOT_FOUND, |
| page_db_.GetSyncMetadata(handler, key, &returned_value)); |
| |
| EXPECT_EQ(Status::OK, page_db_.SetSyncMetadata(handler, key, value)); |
| EXPECT_EQ(Status::OK, |
| page_db_.GetSyncMetadata(handler, key, &returned_value)); |
| EXPECT_EQ(value, returned_value); |
| } |
| }); |
| } |
| |
| TEST_F(PageDbTest, PageIsOnline) { |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| bool page_is_online; |
| |
| // Check that the initial state is not online. |
| page_db_.IsPageOnline(handler, &page_is_online); |
| EXPECT_FALSE(page_is_online); |
| |
| // Mark page as online and check it was updated. |
| EXPECT_EQ(Status::OK, page_db_.MarkPageOnline(handler)); |
| page_db_.IsPageOnline(handler, &page_is_online); |
| EXPECT_TRUE(page_is_online); |
| }); |
| } |
| |
| // This test reproduces the crash of LE-451. The crash is due to a subtle |
| // ordering of coroutine execution that is exactly reproduced here. |
| TEST_F(PageDbTest, LE_451_ReproductionTest) { |
| auto id = RandomObjectIdentifier(environment_.random()); |
| RunInCoroutine([&](CoroutineHandler* handler) { |
| EXPECT_EQ(Status::OK, page_db_.WriteObject( |
| handler, id, DataSource::DataChunk::Create(""), |
| PageDbObjectStatus::LOCAL)); |
| }); |
| CoroutineHandler* handler1 = nullptr; |
| CoroutineHandler* handler2 = nullptr; |
| environment_.coroutine_service()->StartCoroutine( |
| [&](CoroutineHandler* handler) { |
| handler1 = handler; |
| std::unique_ptr<PageDb::Batch> batch; |
| EXPECT_EQ(Status::OK, page_db_.StartBatch(handler, &batch)); |
| EXPECT_EQ(Status::OK, batch->SetObjectStatus( |
| handler, id, PageDbObjectStatus::SYNCED)); |
| if (handler->Yield() == coroutine::ContinuationStatus::INTERRUPTED) { |
| return; |
| } |
| EXPECT_EQ(Status::OK, batch->Execute(handler)); |
| handler1 = nullptr; |
| }); |
| environment_.coroutine_service()->StartCoroutine( |
| [&](CoroutineHandler* handler) { |
| handler2 = handler; |
| std::unique_ptr<PageDb::Batch> batch; |
| EXPECT_EQ(Status::OK, page_db_.StartBatch(handler, &batch)); |
| if (handler->Yield() == coroutine::ContinuationStatus::INTERRUPTED) { |
| return; |
| } |
| EXPECT_EQ(Status::OK, batch->SetObjectStatus( |
| handler, id, PageDbObjectStatus::LOCAL)); |
| EXPECT_EQ(Status::OK, batch->Execute(handler)); |
| handler2 = nullptr; |
| }); |
| ASSERT_TRUE(handler1); |
| ASSERT_TRUE(handler2); |
| |
| // Reach the 2 yield points. |
| RunLoopUntilIdle(); |
| |
| // Posting a task at this level ensures that the right interleaving between |
| // reading and writing object status happens. |
| async::PostTask(dispatcher(), |
| [&] { handler1->Resume(coroutine::ContinuationStatus::OK); }); |
| handler2->Resume(coroutine::ContinuationStatus::OK); |
| |
| // Finish the test. |
| RunLoopUntilIdle(); |
| |
| // Ensures both coroutines are terminated. |
| ASSERT_FALSE(handler1); |
| ASSERT_FALSE(handler2); |
| } |
| |
| } // namespace |
| } // namespace storage |