From 898cc1fa0e6304aa66c99136da99d1a9a07f7239 Mon Sep 17 00:00:00 2001 From: ZhuangYumin Date: Thu, 25 Apr 2024 11:18:01 +0000 Subject: [PATCH] write Page Guard --- bpt/include/bpt/buffer_pool_manager.h | 225 +++++++++++++++++++++++++- bpt/src/buffer_pool_manager.cpp | 105 ++++++++++++ test/CMakeLists.txt | 4 +- test/page_guard_test.cpp | 34 ++++ 4 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 test/page_guard_test.cpp diff --git a/bpt/include/bpt/buffer_pool_manager.h b/bpt/include/bpt/buffer_pool_manager.h index 741b62e..59d681f 100644 --- a/bpt/include/bpt/buffer_pool_manager.h +++ b/bpt/include/bpt/buffer_pool_manager.h @@ -17,14 +17,229 @@ class Page { friend BufferPoolManager; void ResetMemory(); char *GetData(); + page_id_t GetPageId(); + /** Acquire the page write latch. */ + inline void WLatch() { rwlatch_.lock(); } + + /** Release the page write latch. */ + inline void WUnlatch() { rwlatch_.unlock(); } + + /** Acquire the page read latch. */ + inline void RLatch() { rwlatch_.lock_shared(); } + + /** Release the page read latch. */ + inline void RUnlatch() { rwlatch_.unlock_shared(); } + + inline size_t GetPinCount() { return pin_count_; } private: - std::shared_mutex latch_; + std::shared_mutex rwlatch_; char *mem; bool is_dirty_; size_t pin_count_; page_id_t page_id_; }; + +class BasicPageGuard { + public: + BasicPageGuard() = default; + + BasicPageGuard(BufferPoolManager *bpm, Page *page) : bpm_(bpm), page_(page) {} + + BasicPageGuard(const BasicPageGuard &) = delete; + auto operator=(const BasicPageGuard &) -> BasicPageGuard & = delete; + + /** + * @brief Move constructor for BasicPageGuard + * + * When you call BasicPageGuard(std::move(other_guard)), you + * expect that the new guard will behave exactly like the other + * one. In addition, the old page guard should not be usable. For + * example, it should not be possible to call .Drop() on both page + * guards and have the pin count decrease by 2. + */ + BasicPageGuard(BasicPageGuard &&that) noexcept; + + /** + * @brief Drop a page guard + * + * Dropping a page guard should clear all contents + * (so that the page guard is no longer useful), and + * it should tell the BPM that we are done using this page, + * per the specification in the writeup. + */ + void Drop(); + + /** + * @brief Move assignment for BasicPageGuard + * + * Similar to a move constructor, except that the move + * assignment assumes that BasicPageGuard already has a page + * being guarded. Think carefully about what should happen when + * a guard replaces its held page with a different one, given + * the purpose of a page guard. + */ + auto operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard &; + + /** + * @brief Destructor for BasicPageGuard + * + * When a page guard goes out of scope, it should behave as if + * the page guard was dropped. + */ + ~BasicPageGuard(); + + auto PageId() -> page_id_t { return page_->GetPageId(); } + + auto GetData() -> const char * { return page_->GetData(); } + + template + auto As() -> const T * { + return reinterpret_cast(GetData()); + } + + auto GetDataMut() -> char * { + is_dirty_ = true; + return page_->GetData(); + } + + template + auto AsMut() -> T * { + return reinterpret_cast(GetDataMut()); + } + + private: + friend class ReadPageGuard; + friend class WritePageGuard; + + BufferPoolManager *bpm_{nullptr}; + Page *page_{nullptr}; + bool is_dirty_{false}; +}; + +class ReadPageGuard { + public: + ReadPageGuard() = default; + ReadPageGuard(BufferPoolManager *bpm, Page *page) : guard_(bpm, page) {} + ReadPageGuard(const ReadPageGuard &) = delete; + auto operator=(const ReadPageGuard &) -> ReadPageGuard & = delete; + + /** + * @brief Move constructor for ReadPageGuard + * + * Very similar to BasicPageGuard. You want to create + * a ReadPageGuard using another ReadPageGuard. Think + * about if there's any way you can make this easier for yourself... + */ + ReadPageGuard(ReadPageGuard &&that) noexcept; + + /** + * @brief Move assignment for ReadPageGuard + * + * Very similar to BasicPageGuard. Given another ReadPageGuard, + * replace the contents of this one with that one. + */ + auto operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard &; + + /** TODO(P1): Add implementation + * + * @brief Drop a ReadPageGuard + * + * ReadPageGuard's Drop should behave similarly to BasicPageGuard, + * except that ReadPageGuard has an additional resource - the latch! + * However, you should think VERY carefully about in which order you + * want to release these resources. + */ + void Drop(); + + /** + * @brief Destructor for ReadPageGuard + * + * Just like with BasicPageGuard, this should behave + * as if you were dropping the guard. + */ + ~ReadPageGuard(); + + auto PageId() -> page_id_t { return guard_.PageId(); } + + auto GetData() -> const char * { return guard_.GetData(); } + + template + auto As() -> const T * { + return guard_.As(); + } + + private: + // You may choose to get rid of this and add your own private variables. + BasicPageGuard guard_; +}; + +class WritePageGuard { + public: + WritePageGuard() = default; + WritePageGuard(BufferPoolManager *bpm, Page *page) : guard_(bpm, page) {} + WritePageGuard(const WritePageGuard &) = delete; + auto operator=(const WritePageGuard &) -> WritePageGuard & = delete; + + /** TODO(P1): Add implementation + * + * @brief Move constructor for WritePageGuard + * + * Very similar to BasicPageGuard. You want to create + * a WritePageGuard using another WritePageGuard. Think + * about if there's any way you can make this easier for yourself... + */ + WritePageGuard(WritePageGuard &&that) noexcept; + + /** TODO(P1): Add implementation + * + * @brief Move assignment for WritePageGuard + * + * Very similar to BasicPageGuard. Given another WritePageGuard, + * replace the contents of this one with that one. + */ + auto operator=(WritePageGuard &&that) noexcept -> WritePageGuard &; + + /** TODO(P1): Add implementation + * + * @brief Drop a WritePageGuard + * + * WritePageGuard's Drop should behave similarly to BasicPageGuard, + * except that WritePageGuard has an additional resource - the latch! + * However, you should think VERY carefully about in which order you + * want to release these resources. + */ + void Drop(); + + /** TODO(P1): Add implementation + * + * @brief Destructor for WritePageGuard + * + * Just like with BasicPageGuard, this should behave + * as if you were dropping the guard. + */ + ~WritePageGuard(); + + auto PageId() -> page_id_t { return guard_.PageId(); } + + auto GetData() -> const char * { return guard_.GetData(); } + + template + auto As() -> const T * { + return guard_.As(); + } + + auto GetDataMut() -> char * { return guard_.GetDataMut(); } + + template + auto AsMut() -> T * { + return guard_.AsMut(); + } + + private: + // You may choose to get rid of this and add your own private variables. + BasicPageGuard guard_; +}; class BufferPoolManager { public: BufferPoolManager() = delete; @@ -80,7 +295,7 @@ class BufferPoolManager { * @param[out] page_id, the id of the new page * @return BasicPageGuard holding a new page */ - // auto NewPageGuarded(page_id_t *page_id) -> BasicPageGuard; + auto NewPageGuarded(page_id_t *page_id) -> BasicPageGuard; /** * @brief Fetch the requested page from the buffer pool. Return nullptr if page_id needs to be fetched from the disk @@ -112,9 +327,9 @@ class BufferPoolManager { * @param page_id, the id of the page to fetch * @return PageGuard holding the fetched page */ - // auto FetchPageBasic(page_id_t page_id) -> BasicPageGuard; - // auto FetchPageRead(page_id_t page_id) -> ReadPageGuard; - // auto FetchPageWrite(page_id_t page_id) -> WritePageGuard; + auto FetchPageBasic(page_id_t page_id) -> BasicPageGuard; + auto FetchPageRead(page_id_t page_id) -> ReadPageGuard; + auto FetchPageWrite(page_id_t page_id) -> WritePageGuard; /** * TODO(P1): Add implementation diff --git a/bpt/src/buffer_pool_manager.cpp b/bpt/src/buffer_pool_manager.cpp index 36e142b..ae0a0cb 100644 --- a/bpt/src/buffer_pool_manager.cpp +++ b/bpt/src/buffer_pool_manager.cpp @@ -6,6 +6,85 @@ Page::Page() : mem(new char[kPageSize]) {} Page::~Page() { delete[] mem; } void Page::ResetMemory() { memset(mem, 0, kPageSize); } char *Page::GetData() { return mem; } +page_id_t Page::GetPageId() { return page_id_; } +BasicPageGuard::BasicPageGuard(BasicPageGuard &&that) noexcept { + bpm_ = that.bpm_; + page_ = that.page_; + is_dirty_ = that.is_dirty_; + that.bpm_ = nullptr; + that.page_ = nullptr; + that.is_dirty_ = false; +} + +void BasicPageGuard::Drop() { + if (bpm_ == nullptr || page_ == nullptr) { + return; + } + bpm_->UnpinPage(page_->GetPageId(), is_dirty_); + bpm_ = nullptr; + page_ = nullptr; + is_dirty_ = false; +} + +auto BasicPageGuard::operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard & { + if (this == &that) { + return *this; + } + Drop(); + bpm_ = that.bpm_; + page_ = that.page_; + is_dirty_ = that.is_dirty_; + that.bpm_ = nullptr; + that.page_ = nullptr; + that.is_dirty_ = false; + return *this; +} + +BasicPageGuard::~BasicPageGuard() { Drop(); }; // NOLINT + +ReadPageGuard::ReadPageGuard(ReadPageGuard &&that) noexcept : guard_(std::move(that.guard_)) {} + +auto ReadPageGuard::operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard & { + if (this == &that) { + return *this; + } + if (guard_.page_ != nullptr) { + guard_.page_->RUnlatch(); + } + guard_ = std::move(that.guard_); + return *this; +} + +void ReadPageGuard::Drop() { + if (guard_.page_ != nullptr) { + guard_.page_->RUnlatch(); + } + guard_.Drop(); +} + +ReadPageGuard::~ReadPageGuard() { Drop(); } // NOLINT + +WritePageGuard::WritePageGuard(WritePageGuard &&that) noexcept : guard_(std::move(that.guard_)) {} + +auto WritePageGuard::operator=(WritePageGuard &&that) noexcept -> WritePageGuard & { + if (this == &that) { + return *this; + } + if (guard_.page_ != nullptr) { + guard_.page_->WUnlatch(); + } + guard_ = std::move(that.guard_); + return *this; +} + +void WritePageGuard::Drop() { + if (guard_.page_ != nullptr) { + guard_.page_->WUnlatch(); + } + guard_.Drop(); +} + +WritePageGuard::~WritePageGuard() { Drop(); } // NOLINT BufferPoolManager::BufferPoolManager(size_t pool_size, size_t replacer_k, DiskManager *disk_manager) : pool_size(pool_size), replacer_k(replacer_k), @@ -166,4 +245,30 @@ auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool { page->ResetMemory(); DeallocatePage(page_id); return true; +} + +auto BufferPoolManager::FetchPageBasic(page_id_t page_id) -> BasicPageGuard { + Page *page = FetchPage(page_id); + return {this, page}; +} + +auto BufferPoolManager::FetchPageRead(page_id_t page_id) -> ReadPageGuard { + Page *page = FetchPage(page_id); + if (page != nullptr) { + page->RLatch(); + } + return {this, page}; +} + +auto BufferPoolManager::FetchPageWrite(page_id_t page_id) -> WritePageGuard { + Page *page = FetchPage(page_id); + if (page != nullptr) { + page->WLatch(); + } + return {this, page}; +} + +auto BufferPoolManager::NewPageGuarded(page_id_t *page_id) -> BasicPageGuard { + Page *page = NewPage(page_id); + return {this, page}; } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8413aba..afab123 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,4 +6,6 @@ endif() add_executable(replacer_test replacer_test.cpp) target_link_libraries(replacer_test bpt GTest::gtest_main) add_executable(buffer_pool_manager_test buffer_pool_manager_test.cpp) -target_link_libraries(buffer_pool_manager_test bpt GTest::gtest_main) \ No newline at end of file +target_link_libraries(buffer_pool_manager_test bpt GTest::gtest_main) +add_executable(page_guard_test page_guard_test.cpp) +target_link_libraries(page_guard_test bpt GTest::gtest_main) \ No newline at end of file diff --git a/test/page_guard_test.cpp b/test/page_guard_test.cpp new file mode 100644 index 0000000..8832a06 --- /dev/null +++ b/test/page_guard_test.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include "bpt/buffer_pool_manager.h" +#include "bpt/config.h" + +TEST(PageGuardTest, DISABLED_SampleTest) { + const std::string db_name = "/tmp/test.db"; + const size_t buffer_pool_size = 5; + const size_t k = 2; + + auto disk_manager = std::make_shared(db_name); + auto bpm = std::make_shared(buffer_pool_size, k, disk_manager.get()); + + page_id_t page_id_temp; + auto *page0 = bpm->NewPage(&page_id_temp); + + auto guarded_page = BasicPageGuard(bpm.get(), page0); + + EXPECT_EQ(page0->GetData(), guarded_page.GetData()); + EXPECT_EQ(page0->GetPageId(), guarded_page.PageId()); + EXPECT_EQ(1, page0->GetPinCount()); + + guarded_page.Drop(); + + EXPECT_EQ(0, page0->GetPinCount()); + + // Shutdown the disk manager and remove the temporary file we created. + disk_manager->Close(); + remove(db_name.c_str()); +}