write Page Guard
This commit is contained in:
@ -17,14 +17,229 @@ class Page {
|
|||||||
friend BufferPoolManager;
|
friend BufferPoolManager;
|
||||||
void ResetMemory();
|
void ResetMemory();
|
||||||
char *GetData();
|
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:
|
private:
|
||||||
std::shared_mutex latch_;
|
std::shared_mutex rwlatch_;
|
||||||
char *mem;
|
char *mem;
|
||||||
bool is_dirty_;
|
bool is_dirty_;
|
||||||
size_t pin_count_;
|
size_t pin_count_;
|
||||||
page_id_t page_id_;
|
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 <class T>
|
||||||
|
auto As() -> const T * {
|
||||||
|
return reinterpret_cast<const T *>(GetData());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetDataMut() -> char * {
|
||||||
|
is_dirty_ = true;
|
||||||
|
return page_->GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
auto AsMut() -> T * {
|
||||||
|
return reinterpret_cast<T *>(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 <class T>
|
||||||
|
auto As() -> const T * {
|
||||||
|
return guard_.As<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <class T>
|
||||||
|
auto As() -> const T * {
|
||||||
|
return guard_.As<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetDataMut() -> char * { return guard_.GetDataMut(); }
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
auto AsMut() -> T * {
|
||||||
|
return guard_.AsMut<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// You may choose to get rid of this and add your own private variables.
|
||||||
|
BasicPageGuard guard_;
|
||||||
|
};
|
||||||
class BufferPoolManager {
|
class BufferPoolManager {
|
||||||
public:
|
public:
|
||||||
BufferPoolManager() = delete;
|
BufferPoolManager() = delete;
|
||||||
@ -80,7 +295,7 @@ class BufferPoolManager {
|
|||||||
* @param[out] page_id, the id of the new page
|
* @param[out] page_id, the id of the new page
|
||||||
* @return BasicPageGuard holding a 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
|
* @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
|
* @param page_id, the id of the page to fetch
|
||||||
* @return PageGuard holding the fetched page
|
* @return PageGuard holding the fetched page
|
||||||
*/
|
*/
|
||||||
// auto FetchPageBasic(page_id_t page_id) -> BasicPageGuard;
|
auto FetchPageBasic(page_id_t page_id) -> BasicPageGuard;
|
||||||
// auto FetchPageRead(page_id_t page_id) -> ReadPageGuard;
|
auto FetchPageRead(page_id_t page_id) -> ReadPageGuard;
|
||||||
// auto FetchPageWrite(page_id_t page_id) -> WritePageGuard;
|
auto FetchPageWrite(page_id_t page_id) -> WritePageGuard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO(P1): Add implementation
|
* TODO(P1): Add implementation
|
||||||
|
@ -6,6 +6,85 @@ Page::Page() : mem(new char[kPageSize]) {}
|
|||||||
Page::~Page() { delete[] mem; }
|
Page::~Page() { delete[] mem; }
|
||||||
void Page::ResetMemory() { memset(mem, 0, kPageSize); }
|
void Page::ResetMemory() { memset(mem, 0, kPageSize); }
|
||||||
char *Page::GetData() { return mem; }
|
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)
|
BufferPoolManager::BufferPoolManager(size_t pool_size, size_t replacer_k, DiskManager *disk_manager)
|
||||||
: pool_size(pool_size),
|
: pool_size(pool_size),
|
||||||
replacer_k(replacer_k),
|
replacer_k(replacer_k),
|
||||||
@ -166,4 +245,30 @@ auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool {
|
|||||||
page->ResetMemory();
|
page->ResetMemory();
|
||||||
DeallocatePage(page_id);
|
DeallocatePage(page_id);
|
||||||
return true;
|
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};
|
||||||
}
|
}
|
@ -6,4 +6,6 @@ endif()
|
|||||||
add_executable(replacer_test replacer_test.cpp)
|
add_executable(replacer_test replacer_test.cpp)
|
||||||
target_link_libraries(replacer_test bpt GTest::gtest_main)
|
target_link_libraries(replacer_test bpt GTest::gtest_main)
|
||||||
add_executable(buffer_pool_manager_test buffer_pool_manager_test.cpp)
|
add_executable(buffer_pool_manager_test buffer_pool_manager_test.cpp)
|
||||||
target_link_libraries(buffer_pool_manager_test bpt GTest::gtest_main)
|
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)
|
34
test/page_guard_test.cpp
Normal file
34
test/page_guard_test.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#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<DiskManager>(db_name);
|
||||||
|
auto bpm = std::make_shared<BufferPoolManager>(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());
|
||||||
|
}
|
Reference in New Issue
Block a user