diff --git a/src/buffer/buffer_pool_manager.cpp b/src/buffer/buffer_pool_manager.cpp index 6c6e76e6d..d200261d3 100644 --- a/src/buffer/buffer_pool_manager.cpp +++ b/src/buffer/buffer_pool_manager.cpp @@ -6,62 +6,329 @@ // // Identification: src/buffer/buffer_pool_manager.cpp // -// Copyright (c) 2015-2021, Carnegie Mellon University Database Group +// Copyright (c) 2015-2024, Carnegie Mellon University Database Group // //===----------------------------------------------------------------------===// #include "buffer/buffer_pool_manager.h" -#include "common/exception.h" -#include "common/macros.h" -#include "storage/page/page_guard.h" - namespace bustub { -BufferPoolManager::BufferPoolManager(size_t pool_size, DiskManager *disk_manager, size_t replacer_k, +/** + * @brief The constructor for a `FrameHeader` that initializes all fields to default values. + * + * See the documentation for `FrameHeader` in "buffer/buffer_pool_manager.h" for more information. + * + * @param frame_id The frame ID / index of the frame we are creating a header for. + */ +FrameHeader::FrameHeader(frame_id_t frame_id) : frame_id_(frame_id), data_(BUSTUB_PAGE_SIZE, 0) { Reset(); } + +/** + * @brief Get a raw const pointer to the frame's data. + * + * @return const char* A pointer to immutable data that the frame stores. + */ +auto FrameHeader::GetData() const -> const char * { return data_.data(); } + +/** + * @brief Get a raw mutable pointer to the frame's data. + * + * @return char* A pointer to mutable data that the frame stores. + */ +auto FrameHeader::GetDataMut() -> char * { return data_.data(); } + +/** + * @brief Resets a `FrameHeader`'s member fields. + */ +void FrameHeader::Reset() { + std::fill(data_.begin(), data_.end(), 0); + pin_count_.store(0); + is_dirty_ = false; +} + +/** + * @brief Creates a new `BufferPoolManager` instance and initializes all fields. + * + * See the documentation for `BufferPoolManager` in "buffer/buffer_pool_manager.h" for more information. + * + * ### Implementation + * + * We have implemented the constructor for you in a way that makes sense with our reference solution. You are free to + * change anything you would like here if it doesn't fit with you implementation. + * + * Be warned, though! If you stray too far away from our guidance, it will be much harder for us to help you. Our + * recommendation would be to first implement the buffer pool manager using the stepping stones we have provided. + * + * Once you have a fully working solution (all Gradescope test cases pass), then you can try more interesting things! + * + * @param num_frames The size of the buffer pool. + * @param disk_manager The disk manager. + * @param k_dist The backward k-distance for the LRU-K replacer. + * @param log_manager The log manager. Please ignore this for P1. + */ +BufferPoolManager::BufferPoolManager(size_t num_frames, DiskManager *disk_manager, size_t k_dist, LogManager *log_manager) - : pool_size_(pool_size), disk_scheduler_(std::make_unique(disk_manager)), log_manager_(log_manager) { - // TODO(students): remove this line after you have implemented the buffer pool manager - throw NotImplementedException( - "BufferPoolManager is not implemented yet. If you have finished implementing BPM, please remove the throw " - "exception line in `buffer_pool_manager.cpp`."); - - // we allocate a consecutive memory space for the buffer pool - pages_ = new Page[pool_size_]; - replacer_ = std::make_unique(pool_size, replacer_k); - - // Initially, every page is in the free list. - for (size_t i = 0; i < pool_size_; ++i) { - free_list_.emplace_back(static_cast(i)); + : num_frames_(num_frames), + next_page_id_(0), + bpm_latch_(std::make_shared()), + replacer_(std::make_shared(num_frames, k_dist)), + disk_scheduler_(std::make_unique(disk_manager)), + log_manager_(log_manager) { + // Not strictly necessary... + std::scoped_lock latch(*bpm_latch_); + + // Initialize the monotonically increasing counter at 0. + next_page_id_.store(0); + + // Allocate all of the in-memory frames up front. + frames_.reserve(num_frames_); + + // The page table should have exactly `num_frames_` slots, corresponding to exactly `num_frames_` frames. + page_table_.reserve(num_frames_); + + // Initialize all of the frame headers, and fill the free frame list with all possible frame IDs (since all frames are + // initially free). + for (size_t i = 0; i < num_frames_; i++) { + frames_.push_back(std::make_shared(i)); + free_frames_.push_back(static_cast(i)); } } -BufferPoolManager::~BufferPoolManager() { delete[] pages_; } +/** + * @brief Destroys the `BufferPoolManager`, freeing up all memory that the buffer pool was using. + */ +BufferPoolManager::~BufferPoolManager() = default; + +/** + * @brief Returns the number of frames that this buffer pool manages. + */ +auto BufferPoolManager::Size() const -> size_t { return num_frames_; } + +/** + * @brief Allocates a new page on disk. + * + * ### Implementation + * + * You will maintain a thread-safe, monotonically increasing counter in the form of a `std::atomic`. + * See the documentation on [atomics](https://en.cppreference.com/w/cpp/atomic/atomic) for more information. + * + * Also, make sure to read the documentation for `DeletePage`! You can assume that you will never run out of disk + * space (via `DiskScheduler::IncreaseDiskSpace`), so this function _cannot_ fail. + * + * Once you have allocated the new page via the counter, make sure to call `DiskScheduler::IncreaseDiskSpace` so you + * have enough space on disk! + * + * TODO(P1): Add implementation. + * + * @return The page ID of the newly allocated page. + */ +auto BufferPoolManager::NewPage() -> page_id_t { UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::NewPage(page_id_t *page_id) -> Page * { return nullptr; } +/** + * @brief Removes a page from the database, both on disk and in memory. + * + * If the page is pinned in the buffer pool, this function does nothing and returns `false`. Otherwise, this function + * removes the page from both disk and memory (if it is still in the buffer pool), returning `true`. + * + * ### Implementation + * + * Think about all of the places a page or a page's metadata could be, and use that to guide you on implementing this + * function. You will probably want to implement this function _after_ you have implemented `CheckedReadPage` and + * `CheckedWritePage`. + * + * Ideally, we would want to ensure that all space on disk is used efficiently. That would mean the space that deleted + * pages on disk used to occupy should somehow be made available to new pages allocated by `NewPage`. + * + * If you would like to attempt this, you are free to do so. However, for this implementation, you are allowed to + * assume you will not run out of disk space and simply keep allocating disk space upwards in `NewPage`. + * + * For (nonexistent) style points, you can still call `DeallocatePage` in case you want to implement something slightly + * more space-efficient in the future. + * + * TODO(P1): Add implementation. + * + * @param page_id The page ID of the page we want to delete. + * @return `false` if the page exists but could not be deleted, `true` if the page didn't exist or deletion succeeded. + */ +auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool { UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::FetchPage(page_id_t page_id, [[maybe_unused]] AccessType access_type) -> Page * { - return nullptr; +/** + * @brief Acquires an optional write-locked guard over a page of data. The user can specify an `AccessType` if needed. + * + * If it is not possible to bring the page of data into memory, this function will return a `std::nullopt`. + * + * Page data can _only_ be accessed via page guards. Users of this `BufferPoolManager` are expected to acquire either a + * `ReadPageGuard` or a `WritePageGuard` depending on the mode in which they would like to access the data, which + * ensures that any access of data is thread-safe. + * + * There can only be 1 `WritePageGuard` reading/writing a page at a time. This allows data access to be both immutable + * and mutable, meaning the thread that owns the `WritePageGuard` is allowed to manipulate the page's data however they + * want. If a user wants to have multiple threads reading the page at the same time, they must acquire a `ReadPageGuard` + * with `CheckedReadPage` instead. + * + * ### Implementation + * + * There are 3 main cases that you will have to implement. The first two are relatively simple: one is when there is + * plenty of available memory, and the other is when we don't actually need to perform any additional I/O. Think about + * what exactly these two cases entail. + * + * The third case is the trickiest, and it is when we do not have any _easily_ available memory at our disposal. The + * buffer pool is tasked with finding memory that it can use to bring in a page of memory, using the replacement + * algorithm you implemented previously to find candidate frames for eviction. + * + * Once the buffer pool has identified a frame for eviction, several I/O operations may be necessary to bring in the + * page of data we want into the frame. + * + * There is likely going to be a lot of shared code with `CheckedReadPage`, so you may find creating helper functions + * useful. + * + * These two functions are the crux of this project, so we won't give you more hints than this. Good luck! + * + * TODO(P1): Add implementation. + * + * @param page_id The ID of the page we want to write to. + * @param access_type The type of page access. + * @return std::optional An optional latch guard where if there are no more free frames (out of memory) + * returns `std::nullopt`, otherwise returns a `WritePageGuard` ensuring exclusive and mutable access to a page's data. + */ +auto BufferPoolManager::CheckedWritePage(page_id_t page_id, AccessType access_type) -> std::optional { + UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::UnpinPage(page_id_t page_id, bool is_dirty, [[maybe_unused]] AccessType access_type) -> bool { - return false; +/** + * @brief Acquires an optional read-locked guard over a page of data. The user can specify an `AccessType` if needed. + * + * If it is not possible to bring the page of data into memory, this function will return a `std::nullopt`. + * + * Page data can _only_ be accessed via page guards. Users of this `BufferPoolManager` are expected to acquire either a + * `ReadPageGuard` or a `WritePageGuard` depending on the mode in which they would like to access the data, which + * ensures that any access of data is thread-safe. + * + * There can be any number of `ReadPageGuard`s reading the same page of data at a time across different threads. + * However, all data access must be immutable. If a user wants to mutate the page's data, they must acquire a + * `WritePageGuard` with `CheckedWritePage` instead. + * + * ### Implementation + * + * See the implementation details of `CheckedWritePage`. + * + * TODO(P1): Add implementation. + * + * @param page_id The ID of the page we want to read. + * @param access_type The type of page access. + * @return std::optional An optional latch guard where if there are no more free frames (out of memory) + * returns `std::nullopt`, otherwise returns a `ReadPageGuard` ensuring shared and read-only access to a page's data. + */ +auto BufferPoolManager::CheckedReadPage(page_id_t page_id, AccessType access_type) -> std::optional { + UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::FlushPage(page_id_t page_id) -> bool { return false; } +/** + * @brief A wrapper around `CheckedWritePage` that unwraps the inner value if it exists. + * + * If `CheckedWritePage` returns a `std::nullopt`, **this function aborts the entire process.** + * + * This function should **only** be used for testing and ergonomic's sake. If it is at all possible that the buffer pool + * manager might run out of memory, then use `CheckedPageWrite` to allow you to handle that case. + * + * See the documentation for `CheckedPageWrite` for more information about implementation. + * + * @param page_id The ID of the page we want to read. + * @param access_type The type of page access. + * @return WritePageGuard A page guard ensuring exclusive and mutable access to a page's data. + */ +auto BufferPoolManager::WritePage(page_id_t page_id, AccessType access_type) -> WritePageGuard { + auto guard_opt = CheckedWritePage(page_id, access_type); -void BufferPoolManager::FlushAllPages() {} + if (!guard_opt.has_value()) { + std::cerr << fmt::format("\n`CheckedPageWrite` failed to bring in page {}\n\n", page_id); + std::abort(); + } -auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool { return false; } + return std::move(guard_opt).value(); +} -auto BufferPoolManager::AllocatePage() -> page_id_t { return next_page_id_++; } +/** + * @brief A wrapper around `CheckedReadPage` that unwraps the inner value if it exists. + * + * If `CheckedReadPage` returns a `std::nullopt`, **this function aborts the entire process.** + * + * This function should **only** be used for testing and ergonomic's sake. If it is at all possible that the buffer pool + * manager might run out of memory, then use `CheckedPageWrite` to allow you to handle that case. + * + * See the documentation for `CheckedPageRead` for more information about implementation. + * + * @param page_id The ID of the page we want to read. + * @param access_type The type of page access. + * @return ReadPageGuard A page guard ensuring shared and read-only access to a page's data. + */ +auto BufferPoolManager::ReadPage(page_id_t page_id, AccessType access_type) -> ReadPageGuard { + auto guard_opt = CheckedReadPage(page_id, access_type); -auto BufferPoolManager::FetchPageBasic(page_id_t page_id) -> BasicPageGuard { return {this, nullptr}; } + if (!guard_opt.has_value()) { + std::cerr << fmt::format("\n`CheckedPageRead` failed to bring in page {}\n\n", page_id); + std::abort(); + } + + return std::move(guard_opt).value(); +} -auto BufferPoolManager::FetchPageRead(page_id_t page_id) -> ReadPageGuard { return {this, nullptr}; } +/** + * @brief Flushes a page's data out to disk. + * + * This function will write out a page's data to disk if it has been modified. If the given page is not in memory, this + * function will return `false`. + * + * ### Implementation + * + * You should probably leave implementing this function until after you have completed `CheckedReadPage` and + * `CheckedWritePage`, as it will likely be much easier to understand what to do. + * + * TODO(P1): Add implementation + * + * @param page_id The page ID of the page to be flushed. + * @return `false` if the page could not be found in the page table, otherwise `true`. + */ +auto BufferPoolManager::FlushPage(page_id_t page_id) -> bool { UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::FetchPageWrite(page_id_t page_id) -> WritePageGuard { return {this, nullptr}; } +/** + * @brief Flushes all page data that is in memory to disk. + * + * ### Implementation + * + * You should probably leave implementing this function until after you have completed `CheckedReadPage`, + * `CheckedWritePage`, and `FlushPage`, as it will likely be much easier to understand what to do. + * + * TODO(P1): Add implementation + */ +void BufferPoolManager::FlushAllPages() { UNIMPLEMENTED("TODO(P1): Add implementation."); } -auto BufferPoolManager::NewPageGuarded(page_id_t *page_id) -> BasicPageGuard { return {this, nullptr}; } +/** + * @brief Retrieves the pin count of a page. If the page does not exist in memory, return `std::nullopt`. + * + * This function is thread safe. Callers may invoke this function in a multi-threaded environment where multiple threads + * access the same page. + * + * This function is intended for testing purposes. If this function is implemented incorrectly, it will definitely cause + * problems with the test suite and autograder. + * + * # Implementation + * + * We will use this function to test if your buffer pool manager is managing pin counts correctly. Since the + * `pin_count_` field in `FrameHeader` is an atomic type, you do not need to take the latch on the frame that holds the + * page we want to look at. Instead, you can simply use an atomic `load` to safely load the value stored. You will still + * need to take the buffer pool latch, however. + * + * Again, if you are unfamiliar with atomic types, see the official C++ docs + * [here](https://en.cppreference.com/w/cpp/atomic/atomic). + * + * TODO(P1): Add implementation + * + * @param page_id The page ID of the page we want to get the pin count of. + * @return std::optional The pin count if the page exists, otherwise `std::nullopt`. + */ +auto BufferPoolManager::GetPinCount(page_id_t page_id) -> std::optional { + UNIMPLEMENTED("TODO(P1): Add implementation."); +} } // namespace bustub diff --git a/src/container/disk/hash/disk_extendible_hash_table_utils.cpp b/src/container/disk/hash/disk_extendible_hash_table_utils.cpp index 2691b63e9..340a39c1d 100644 --- a/src/container/disk/hash/disk_extendible_hash_table_utils.cpp +++ b/src/container/disk/hash/disk_extendible_hash_table_utils.cpp @@ -40,8 +40,8 @@ template void DiskExtendibleHashTable::PrintHT() const { std::cout << "\n"; std::cout << "==================== PRINT! ====================\n"; - BasicPageGuard header_guard = bpm_->FetchPageBasic(header_page_id_); - auto *header = header_guard.As(); + ReadPageGuard header_guard = bpm_->ReadPage(header_page_id_); + const auto *header = header_guard.As(); header->PrintHeader(); @@ -51,16 +51,16 @@ void DiskExtendibleHashTable::PrintHT() const { std::cout << "Directory " << idx << ", page id: " << directory_page_id << "\n"; continue; } - BasicPageGuard directory_guard = bpm_->FetchPageBasic(directory_page_id); - auto *directory = directory_guard.As(); + ReadPageGuard directory_guard = bpm_->ReadPage(directory_page_id); + const auto *directory = directory_guard.As(); std::cout << "Directory " << idx << ", page id: " << directory_page_id << "\n"; directory->PrintDirectory(); for (uint32_t idx2 = 0; idx2 < directory->Size(); idx2++) { page_id_t bucket_page_id = directory->GetBucketPageId(idx2); - BasicPageGuard bucket_guard = bpm_->FetchPageBasic(bucket_page_id); - auto *bucket = bucket_guard.As>(); + ReadPageGuard bucket_guard = bpm_->ReadPage(bucket_page_id); + const auto *bucket = bucket_guard.As>(); std::cout << "Bucket " << idx2 << ", page id: " << bucket_page_id << "\n"; bucket->PrintBucket(); @@ -77,15 +77,15 @@ void DiskExtendibleHashTable::PrintHT() const { template void DiskExtendibleHashTable::VerifyIntegrity() const { BUSTUB_ASSERT(header_page_id_ != INVALID_PAGE_ID, "header page id is invalid"); - BasicPageGuard header_guard = bpm_->FetchPageBasic(header_page_id_); - auto *header = header_guard.As(); + ReadPageGuard header_guard = bpm_->ReadPage(header_page_id_); + const auto *header = header_guard.As(); // for each of the directory pages, check their integrity using directory page VerifyIntegrity for (uint32_t idx = 0; idx < header->MaxSize(); idx++) { auto directory_page_id = header->GetDirectoryPageId(idx); if (static_cast(directory_page_id) != INVALID_PAGE_ID) { - BasicPageGuard directory_guard = bpm_->FetchPageBasic(directory_page_id); - auto *directory = directory_guard.As(); + ReadPageGuard directory_guard = bpm_->ReadPage(directory_page_id); + const auto *directory = directory_guard.As(); directory->VerifyIntegrity(); } } diff --git a/src/include/buffer/buffer_pool_manager.h b/src/include/buffer/buffer_pool_manager.h index 595eb03f7..b7449a3ff 100644 --- a/src/include/buffer/buffer_pool_manager.h +++ b/src/include/buffer/buffer_pool_manager.h @@ -6,7 +6,7 @@ // // Identification: src/include/buffer/buffer_pool_manager.h // -// Copyright (c) 2015-2021, Carnegie Mellon University Database Group +// Copyright (c) 2015-2024, Carnegie Mellon University Database Group // //===----------------------------------------------------------------------===// @@ -14,202 +14,161 @@ #include #include -#include // NOLINT +#include #include +#include #include "buffer/lru_k_replacer.h" #include "common/config.h" #include "recovery/log_manager.h" #include "storage/disk/disk_scheduler.h" -#include "storage/disk/write_back_cache.h" #include "storage/page/page.h" #include "storage/page/page_guard.h" namespace bustub { +class BufferPoolManager; +class ReadPageGuard; +class WritePageGuard; + /** - * BufferPoolManager reads disk pages to and from its internal buffer pool. + * @brief A helper class for `BufferPoolManager` that manages a frame of memory and related metadata. + * + * This class represents headers for frames of memory that the `BufferPoolManager` stores pages of data into. Note that + * the actual frames of memory are not stored directly inside a `FrameHeader`, rather the `FrameHeader`s store pointer + * to the frames and are stored separately them. + * + * --- + * + * Something that may (or may not) be of interest to you is why the field `data_` is stored as a vector that is + * allocated on the fly instead of as a direct pointer to some pre-allocated chunk of memory. + * + * In a traditional production buffer pool manager, all memory that the buffer pool is intended to manage is allocated + * in one large contiguous array (think of a very large `malloc` call that allocates several gigabytes of memory up + * front). This large contiguous block of memory is then divided into contiguous frames. In other words, frames are + * defined by an offset from the base of the array in page-sized (4 KB) intervals. + * + * In BusTub, we instead allocate each frame on its own (via a `std::vector`) in order to easily detect buffer + * overflow with address sanitizer. Since C++ has no notion of memory safety, it would be very easy to cast a page's + * data pointer into some large data type and start overwriting other pages of data if they were all contiguous. + * + * If you would like to attempt to use more efficient data structures for your buffer pool manager, you are free to do + * so. However, you will likely benefit significantly from detecting buffer overflow in future projects (especially + * project 2). */ -class BufferPoolManager { +class FrameHeader { + friend class BufferPoolManager; + friend class ReadPageGuard; + friend class WritePageGuard; + public: - /** - * @brief Creates a new BufferPoolManager. - * @param pool_size the size of the buffer pool - * @param disk_manager the disk manager - * @param replacer_k the LookBack constant k for the LRU-K replacer - * @param log_manager the log manager (for testing only: nullptr = disable logging). Please ignore this for P1. - */ - BufferPoolManager(size_t pool_size, DiskManager *disk_manager, size_t replacer_k = LRUK_REPLACER_K, - LogManager *log_manager = nullptr); + explicit FrameHeader(frame_id_t frame_id); - /** - * @brief Destroy an existing BufferPoolManager. - */ - ~BufferPoolManager(); + private: + auto GetData() const -> const char *; + auto GetDataMut() -> char *; + void Reset(); - /** @brief Return the size (number of frames) of the buffer pool. */ - auto GetPoolSize() -> size_t { return pool_size_; } + /** @brief The frame ID / index of the frame this header represents. */ + const frame_id_t frame_id_; - /** @brief Return the pointer to all the pages in the buffer pool. */ - auto GetPages() -> Page * { return pages_; } + /** @brief The readers / writer latch for this frame. */ + std::shared_mutex rwlatch_; - /** - * TODO(P1): Add implementation - * - * @brief Create a new page in the buffer pool. Set page_id to the new page's id, or nullptr if all frames - * are currently in use and not evictable (in another word, pinned). - * - * You should pick the replacement frame from either the free list or the replacer (always find from the free list - * first), and then call the AllocatePage() method to get a new page id. If the replacement frame has a dirty page, - * you should write it back to the disk first. You also need to reset the memory and metadata for the new page. - * - * Remember to "Pin" the frame by calling replacer.SetEvictable(frame_id, false) - * so that the replacer wouldn't evict the frame before the buffer pool manager "Unpin"s it. - * Also, remember to record the access history of the frame in the replacer for the lru-k algorithm to work. - * - * @param[out] page_id id of created page - * @return nullptr if no new pages could be created, otherwise pointer to new page - */ - auto NewPage(page_id_t *page_id) -> Page *; + /** @brief The number of pins on this frame keeping the page in memory. */ + std::atomic pin_count_; - /** - * TODO(P1): Add implementation - * - * @brief PageGuard wrapper for NewPage - * - * Functionality should be the same as NewPage, except that - * instead of returning a pointer to a page, you return a - * BasicPageGuard structure. - * - * @param[out] page_id, the id of the new page - * @return BasicPageGuard holding a new page - */ - auto NewPageGuarded(page_id_t *page_id) -> BasicPageGuard; + /** @brief The dirty flag. */ + bool is_dirty_; /** - * TODO(P1): Add implementation + * @brief A pointer to the data of the page that this frame holds. * - * @brief Fetch the requested page from the buffer pool. Return nullptr if page_id needs to be fetched from the disk - * but all frames are currently in use and not evictable (in another word, pinned). - * - * First search for page_id in the buffer pool. If not found, pick a replacement frame from either the free list or - * the replacer (always find from the free list first), read the page from disk by scheduling a read DiskRequest with - * disk_scheduler_->Schedule(), and replace the old page in the frame. Similar to NewPage(), if the old page is dirty, - * you need to write it back to disk and update the metadata of the new page - * - * In addition, remember to disable eviction and record the access history of the frame like you did for NewPage(). - * - * @param page_id id of page to be fetched - * @param access_type type of access to the page, only needed for leaderboard tests. - * @return nullptr if page_id cannot be fetched, otherwise pointer to the requested page + * If the frame does not hold any page data, the frame contains all null bytes. */ - auto FetchPage(page_id_t page_id, AccessType access_type = AccessType::Unknown) -> Page *; + std::vector data_; /** - * TODO(P1): Add implementation - * - * @brief PageGuard wrappers for FetchPage + * TODO(P1): You may add any fields or helper functions under here that you think are necessary. * - * Functionality should be the same as FetchPage, except - * that, depending on the function called, a guard is returned. - * If FetchPageRead or FetchPageWrite is called, it is expected that - * the returned page already has a read or write latch held, respectively. - * - * @param page_id, the id of the page to fetch - * @return PageGuard holding the fetched page + * One potential optimization you could make is storing an optional page ID of the page that the `FrameHeader` is + * currently storing. This might allow you to skip searching for the corresponding (page ID, frame ID) pair somewhere + * else in the buffer pool manager... */ - 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 - * - * @brief Unpin the target page from the buffer pool. If page_id is not in the buffer pool or its pin count is already - * 0, return false. - * - * Decrement the pin count of a page. If the pin count reaches 0, the frame should be evictable by the replacer. - * Also, set the dirty flag on the page to indicate if the page was modified. - * - * @param page_id id of page to be unpinned - * @param is_dirty true if the page should be marked as dirty, false otherwise - * @param access_type type of access to the page, only needed for leaderboard tests. - * @return false if the page is not in the page table or its pin count is <= 0 before this call, true otherwise - */ - auto UnpinPage(page_id_t page_id, bool is_dirty, AccessType access_type = AccessType::Unknown) -> bool; +/** + * @brief The declaration of the `BufferPoolManager` class. + * + * As stated in the writeup, the buffer pool is responsible for moving physical pages of data back and forth from + * buffers in main memory to persistent storage. It also behaves as a cache, keeping frequently used pages in memory for + * faster access, and evicting unused or cold pages back out to storage. + * + * Make sure you read the writeup in its entirety before attempting to implement the buffer pool manager. You also need + * to have completed the implementation of both the `LRUKReplacer` and `DiskManager` classes. + */ +class BufferPoolManager { + public: + BufferPoolManager(size_t num_frames, DiskManager *disk_manager, size_t k_dist = LRUK_REPLACER_K, + LogManager *log_manager = nullptr); + ~BufferPoolManager(); - /** - * TODO(P1): Add implementation - * - * @brief Flush the target page to disk. - * - * Use the DiskManager::WritePage() method to flush a page to disk, REGARDLESS of the dirty flag. - * Unset the dirty flag of the page after flushing. - * - * @param page_id id of page to be flushed, cannot be INVALID_PAGE_ID - * @return false if the page could not be found in the page table, true otherwise - */ + auto Size() const -> size_t; + auto NewPage() -> page_id_t; + auto DeletePage(page_id_t page_id) -> bool; + auto CheckedWritePage(page_id_t page_id, AccessType access_type = AccessType::Unknown) + -> std::optional; + auto CheckedReadPage(page_id_t page_id, AccessType access_type = AccessType::Unknown) -> std::optional; + auto WritePage(page_id_t page_id, AccessType access_type = AccessType::Unknown) -> WritePageGuard; + auto ReadPage(page_id_t page_id, AccessType access_type = AccessType::Unknown) -> ReadPageGuard; auto FlushPage(page_id_t page_id) -> bool; - - /** - * TODO(P1): Add implementation - * - * @brief Flush all the pages in the buffer pool to disk. - */ void FlushAllPages(); + auto GetPinCount(page_id_t page_id) -> std::optional; + + private: + /** @brief The number of frames in the buffer pool. */ + const size_t num_frames_; + + /** @brief The next page ID to be allocated. */ + std::atomic next_page_id_; /** - * TODO(P1): Add implementation - * - * @brief Delete a page from the buffer pool. If page_id is not in the buffer pool, do nothing and return true. If the - * page is pinned and cannot be deleted, return false immediately. - * - * After deleting the page from the page table, stop tracking the frame in the replacer and add the frame - * back to the free list. Also, reset the page's memory and metadata. Finally, you should call DeallocatePage() to - * imitate freeing the page on the disk. + * @brief The latch protecting the buffer pool's inner data structures. * - * @param page_id id of page to be deleted - * @return false if the page exists but could not be deleted, true if the page didn't exist or deletion succeeded + * TODO(P1) We recommend replacing this comment with details about what this latch actually protects. */ - auto DeletePage(page_id_t page_id) -> bool; + std::shared_ptr bpm_latch_; - private: - /** Number of pages in the buffer pool. */ - const size_t pool_size_; - /** The next page id to be allocated */ - std::atomic next_page_id_ = 0; - - /** Array of buffer pool pages. */ - Page *pages_; - /** Pointer to the disk scheduler. */ - std::unique_ptr disk_scheduler_ __attribute__((__unused__)); - /** Pointer to the log manager. Please ignore this for P1. */ - LogManager *log_manager_ __attribute__((__unused__)); - /** Page table for keeping track of buffer pool pages. */ + /** @brief The frame headers of the frames that this buffer pool manages. */ + std::vector> frames_; + + /** @brief The page table that keeps track of the mapping between pages and buffer pool frames. */ std::unordered_map page_table_; - /** Replacer to find unpinned pages for replacement. */ - std::unique_ptr replacer_; - /** List of free frames that don't have any pages on them. */ - std::list free_list_; - /** This latch protects shared data structures. We recommend updating this comment to describe what it protects. */ - std::mutex latch_; - /** This buffer is for the leaderboard task. You may want to use it to optimize the write requests. */ - WriteBackCache write_back_cache_ __attribute__((__unused__)); + + /** @brief A list of free frames that do not hold any page's data. */ + std::list free_frames_; + + /** @brief The replacer to find unpinned / candidate pages for eviction. */ + std::shared_ptr replacer_; + + /** @brief A pointer to the disk scheduler. */ + std::unique_ptr disk_scheduler_; /** - * @brief Allocate a page on disk. Caller should acquire the latch before calling this function. - * @return the id of the allocated page + * @brief A pointer to the log manager. + * + * Note: Please ignore this for P1. */ - auto AllocatePage() -> page_id_t; + LogManager *log_manager_ __attribute__((__unused__)); /** - * @brief Deallocate a page on disk. Caller should acquire the latch before calling this function. - * @param page_id id of the page to deallocate + * TODO(P1): You may add additional private members and helper functions if you find them necessary. + * + * There will likely be a lot of code duplication between the different modes of accessing a page. + * + * We would recommend implementing a helper function that returns the ID of a frame that is free and has nothing + * stored inside of it. Additionally, you may also want to implement a helper function that returns a shared pointer + * to a `FrameHeader` that already has a page's data stored inside of it. */ - void DeallocatePage(__attribute__((unused)) page_id_t page_id) { - // This is a no-nop right now without a more complex data structure to track deallocated pages - } - - // TODO(student): You may add additional private members and helper functions }; } // namespace bustub diff --git a/src/include/common/config.h b/src/include/common/config.h index fb004eba1..d81261151 100644 --- a/src/include/common/config.h +++ b/src/include/common/config.h @@ -33,12 +33,11 @@ extern std::chrono::duration log_timeout; static constexpr int INVALID_PAGE_ID = -1; // invalid page id static constexpr int INVALID_TXN_ID = -1; // invalid transaction id static constexpr int INVALID_LSN = -1; // invalid log sequence number -static constexpr int HEADER_PAGE_ID = 0; // the header page id static constexpr int BUSTUB_PAGE_SIZE = 4096; // size of a data page in byte static constexpr int BUFFER_POOL_SIZE = 10; // size of buffer pool static constexpr int LOG_BUFFER_SIZE = ((BUFFER_POOL_SIZE + 1) * BUSTUB_PAGE_SIZE); // size of a log buffer in byte static constexpr int BUCKET_SIZE = 50; // size of extendible hash bucket -static constexpr int LRUK_REPLACER_K = 10; // lookback window for lru-k replacer +static constexpr int LRUK_REPLACER_K = 10; // default lookback window for lru-k replacer using frame_id_t = int32_t; // frame id type using page_id_t = int32_t; // page id type diff --git a/src/include/storage/disk/disk_manager.h b/src/include/storage/disk/disk_manager.h index 3e3fa6b46..d23c66cf1 100644 --- a/src/include/storage/disk/disk_manager.h +++ b/src/include/storage/disk/disk_manager.h @@ -45,6 +45,15 @@ class DiskManager { */ void ShutDown(); + /** + * @brief Increases the size of the database file. + * + * This function works like a dynamic array, where the capacity is doubled until all pages can fit. + * + * @param pages The number of pages the caller wants the file used for storage to support. + */ + virtual void IncreaseDiskSpace(size_t pages); + /** * Write a page to the database file. * @param page_id id of the page @@ -110,6 +119,11 @@ class DiskManager { std::future *flush_log_f_{nullptr}; // With multiple buffer pool instances, need to protect file access std::mutex db_io_latch_; + + /** @brief The number of pages allocated to the DBMS on disk. */ + size_t pages_; + /** @brief The capacity of the file used for storage on disk. */ + size_t page_capacity_; }; } // namespace bustub diff --git a/src/include/storage/disk/disk_manager_memory.h b/src/include/storage/disk/disk_manager_memory.h index 057dc6150..ff67f4f2e 100644 --- a/src/include/storage/disk/disk_manager_memory.h +++ b/src/include/storage/disk/disk_manager_memory.h @@ -9,7 +9,11 @@ // Copyright (c) 2015-2020, Carnegie Mellon University Database Group // //===----------------------------------------------------------------------===// + +#include "storage/disk/disk_manager.h" + #include +#include #include // NOLINT #include #include @@ -27,10 +31,12 @@ #include "common/exception.h" #include "common/logger.h" #include "fmt/core.h" -#include "storage/disk/disk_manager.h" namespace bustub { +/** @brief The default size of the database file. */ +static const size_t DEFAULT_DB_IO_SIZE = 16; + /** * DiskManagerMemory replicates the utility of DiskManager on memory. It is primarily used for * data structure performance testing. @@ -41,6 +47,12 @@ class DiskManagerMemory : public DiskManager { ~DiskManagerMemory() override { delete[] memory_; } + /** + * This function should increase the disk space, but since we have a fixed amount of memory we just check that the + * pages are in bounds. + */ + void IncreaseDiskSpace(size_t pages) override; + /** * Write a page to the database file. * @param page_id id of the page @@ -56,6 +68,7 @@ class DiskManagerMemory : public DiskManager { void ReadPage(page_id_t page_id, char *page_data) override; private: + size_t pages_; char *memory_; }; @@ -65,7 +78,31 @@ class DiskManagerMemory : public DiskManager { */ class DiskManagerUnlimitedMemory : public DiskManager { public: - DiskManagerUnlimitedMemory() { std::fill(recent_access_.begin(), recent_access_.end(), -1); } + DiskManagerUnlimitedMemory() { + std::scoped_lock l(mutex_); + while (data_.size() < pages_ + 1) { + data_.push_back(std::make_shared()); + } + std::fill(recent_access_.begin(), recent_access_.end(), -1); + } + + /** + * This function should increase the disk space, but since this is memory we just resize the vector. + */ + void IncreaseDiskSpace(size_t pages) override { + std::scoped_lock l(mutex_); + + if (pages < pages_) { + return; + } + + while (data_.size() < pages + 1) { + data_.push_back(std::make_shared()); + } + assert(data_.size() == pages + 1); + + pages_ = pages; + } /** * Write a page to the database file. @@ -112,7 +149,7 @@ class DiskManagerUnlimitedMemory : public DiskManager { return; } if (data_[page_id] == nullptr) { - fmt::println(stderr, "page {} not exist", page_id); + fmt::println(stderr, "page {} not exist", page_id, pages_); std::terminate(); return; } @@ -174,6 +211,8 @@ class DiskManagerUnlimitedMemory : public DiskManager { std::mutex mutex_; std::optional thread_id_; std::vector> data_; + + size_t pages_{DEFAULT_DB_IO_SIZE}; }; } // namespace bustub diff --git a/src/include/storage/disk/disk_scheduler.h b/src/include/storage/disk/disk_scheduler.h index eee9f0f91..da226c6bb 100644 --- a/src/include/storage/disk/disk_scheduler.h +++ b/src/include/storage/disk/disk_scheduler.h @@ -6,7 +6,7 @@ // // Identification: src/include/storage/disk/disk_scheduler.h // -// Copyright (c) 2015-2023, Carnegie Mellon University Database Group +// Copyright (c) 2015-2024, Carnegie Mellon University Database Group // //===----------------------------------------------------------------------===// @@ -83,6 +83,25 @@ class DiskScheduler { */ auto CreatePromise() -> DiskSchedulerPromise { return {}; }; + /** + * @brief Increases the size of the database file to fit the specified number of pages. + * + * This function works like a dynamic array, where the capacity is doubled until all pages can fit. + * + * @param pages The number of pages the caller wants the file used for storage to support. + */ + void IncreaseDiskSpace(size_t pages) { disk_manager_->IncreaseDiskSpace(pages); } + + /** + * @brief Deallocates a page on disk. + * + * Note: You should look at the documentation for `DeletePage` in `BufferPoolManager` before using this method. + * Also note: This is a no-op without a more complex data structure to track deallocated pages. + * + * @param page_id The page ID of the page to deallocate from disk. + */ + void DeallocatePage(page_id_t page_id) {} + private: /** Pointer to the disk manager. */ DiskManager *disk_manager_ __attribute__((__unused__)); diff --git a/src/include/storage/disk/write_back_cache.h b/src/include/storage/disk/write_back_cache.h deleted file mode 100644 index 1a09b59ab..000000000 --- a/src/include/storage/disk/write_back_cache.h +++ /dev/null @@ -1,97 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// BusTub -// -// write_back_cache.h -// -// Identification: src/include/storage/disk/write_back_cache.h -// -// Copyright (c) 2015-2024, Carnegie Mellon University Database Group -// -//===----------------------------------------------------------------------===// - -#pragma once - -#include -#include -#include "common/config.h" -#include "common/macros.h" -#include "storage/page/page.h" - -namespace bustub { - -/** - * WriteBackCache provides extra memory space other than the buffer pool to store the pages. It's purpose - * is to gather the copy of pages that are about to be written back to disk, so that the bpm doesn't have - * to incur IO penality and wait for the write to be completed when evicting. - * Spring 24: The cache is limited to store a constant number of pages in total (8). - * !! ANY ATTEMPTS TO ADD ANOTHER IN-MEMORY CACHE WILL BE REVIEWED MANUALLY AS PER LEADERBOARD POLICY !! - */ -class WriteBackCache { - public: - WriteBackCache() : write_back_pages_{new Page[8]} {} - ~WriteBackCache() { delete[] write_back_pages_; } - DISALLOW_COPY_AND_MOVE(WriteBackCache); - - /** - * @brief Adds a new page to the write back cache. - * @param page the page pointer from the bpm that is about to be evicted. - * @return pointer to the copied page in the cache, or nullptr if the cache is full. - */ - auto Add(Page *page) -> Page * { - if ((page == nullptr) || IsFull()) { - return nullptr; - } - - uint32_t slot = FindFreeSlot(); - memcpy(write_back_pages_[slot].GetData(), page->GetData(), BUSTUB_PAGE_SIZE); - MarkSlotUsed(slot); - - return write_back_pages_ + slot; - } - - /** - * @brief Removes a page from the write back cache. - * @param page the pointer previously returned by Add. - */ - auto Remove(Page *page) -> void { - if (page != nullptr) { - MarkSlotFree(page - write_back_pages_); - } - } - - private: - /** @brief Whether the cache is full. */ - auto IsFull() -> bool { return free_slot_bitmap_ == 0xFFU; } - - /** @brief Finds a free slot in the cache, if not full. */ - auto FindFreeSlot() -> uint32_t { - BUSTUB_ASSERT(!IsFull(), "no free slot in write back cache"); - uint32_t i = 0; - uint8_t bitmap = free_slot_bitmap_; - while ((bitmap & 1U) != 0) { - bitmap >>= 1; - i++; - } - return i; - } - - /** @brief Marks a free slot as used. */ - void MarkSlotUsed(uint32_t slot) { - BUSTUB_ASSERT(((free_slot_bitmap_ >> slot) & 1U) == 0, "slot has already been used"); - free_slot_bitmap_ |= (1U << slot); - } - - /** @brief Marks a used slot as free. */ - void MarkSlotFree(uint32_t slot) { - BUSTUB_ASSERT(((free_slot_bitmap_ >> slot) & 1U) == 1, "slot is already free"); - free_slot_bitmap_ &= ~(1U << slot); - } - - /** The array of write back cache pages. */ - Page *write_back_pages_; - /** The bitmap that records which slots are free. */ - uint8_t free_slot_bitmap_{0}; -}; - -} // namespace bustub diff --git a/src/include/storage/page/page.h b/src/include/storage/page/page.h index 63009fe8d..7a2e15033 100644 --- a/src/include/storage/page/page.h +++ b/src/include/storage/page/page.h @@ -14,6 +14,7 @@ #include #include +#include #include "common/config.h" #include "common/rwlatch.h" @@ -21,6 +22,10 @@ namespace bustub { /** + * @brief + * + * TODO(2024 tas) This entire class needs to be updated. + * * Page is the basic unit of storage within the database system. Page provides a wrapper for actual data pages being * held in main memory. Page also contains book-keeping information that is used by the buffer pool manager, e.g. * pin count, dirty flag, page id, etc. @@ -28,40 +33,43 @@ namespace bustub { class Page { // There is book-keeping information inside the page that should only be relevant to the buffer pool manager. friend class BufferPoolManager; + friend class ReadPageGuard; + friend class WritePageGuard; public: /** Constructor. Zeros out the page data. */ - Page() { - data_ = new char[BUSTUB_PAGE_SIZE]; + Page() : data_(static_cast(BUSTUB_PAGE_SIZE), 0), page_id_(INVALID_PAGE_ID) { + pin_count_.store(0, std::memory_order_relaxed); + is_dirty_.store(false, std::memory_order_relaxed); ResetMemory(); } /** Default destructor. */ - ~Page() { delete[] data_; } + ~Page() = default; /** @return the actual data contained within this page */ - inline auto GetData() -> char * { return data_; } + inline auto GetData() -> char * { return data_.data(); } /** @return the page id of this page */ - inline auto GetPageId() -> page_id_t { return page_id_; } + inline auto GetPageId() const -> page_id_t { return page_id_; } - /** @return the pin count of this page */ - inline auto GetPinCount() -> int { return pin_count_; } + /** @return the pin count of this page. */ + inline auto GetPinCount() const -> int { return pin_count_.load(std::memory_order_acquire); } /** @return true if the page in memory has been modified from the page on disk, false otherwise */ - inline auto IsDirty() -> bool { return is_dirty_; } + inline auto IsDirty() const -> bool { return is_dirty_.load(std::memory_order_acquire); } /** Acquire the page write latch. */ - inline void WLatch() { rwlatch_.WLock(); } + inline void WLatch() { rwlatch_.lock(); } /** Release the page write latch. */ - inline void WUnlatch() { rwlatch_.WUnlock(); } + inline void WUnlatch() { rwlatch_.unlock(); } /** Acquire the page read latch. */ - inline void RLatch() { rwlatch_.RLock(); } + inline void RLatch() { rwlatch_.lock_shared(); } /** Release the page read latch. */ - inline void RUnlatch() { rwlatch_.RUnlock(); } + inline void RUnlatch() { rwlatch_.unlock_shared(); } /** @return the page LSN. */ inline auto GetLSN() -> lsn_t { return *reinterpret_cast(GetData() + OFFSET_LSN); } @@ -78,21 +86,37 @@ class Page { static constexpr size_t OFFSET_LSN = 4; private: - /** Zeroes out the data that is held within the page. */ - inline void ResetMemory() { memset(data_, OFFSET_PAGE_START, BUSTUB_PAGE_SIZE); } - - /** The actual data that is stored within a page. */ - // Usually this should be stored as `char data_[BUSTUB_PAGE_SIZE]{};`. But to enable ASAN to detect page overflow, - // we store it as a ptr. - char *data_; - /** The ID of this page. */ - page_id_t page_id_ = INVALID_PAGE_ID; - /** The pin count of this page. */ - int pin_count_ = 0; - /** True if the page is dirty, i.e. it is different from its corresponding page on disk. */ - bool is_dirty_ = false; - /** Page latch. */ - ReaderWriterLatch rwlatch_; + /** @brief Resets the frame. + * + * Zeroes out the data that is held within the frame and sets all fields to default values. + */ + inline void ResetMemory() { + std::fill(data_.begin(), data_.end(), 0); + page_id_ = INVALID_PAGE_ID; + pin_count_.store(0, std::memory_order_release); + is_dirty_.store(false, std::memory_order_release); + } + + /** @brief The actual data that is stored within a page. + * + * In practice, this should be stored as a `char data_[BUSTUB_PAGE_SIZE]`. + * However, in order to allow address sanitizer to detect buffer overflow, we store it as a vector. + * + * Note that friend classes should make sure not increase the size of this data field. + */ + std::vector data_; + + /** @brief The ID of this page. */ + page_id_t page_id_; + + /** @brief The pin count of this page. */ + std::atomic pin_count_; + + /** @brief True if the page is dirty, i.e. it is different from its corresponding page on disk. */ + std::atomic is_dirty_; + + /** @brief The page latch protecting data access. */ + std::shared_mutex rwlatch_; }; } // namespace bustub diff --git a/src/include/storage/page/page_guard.h b/src/include/storage/page/page_guard.h index c7e7d3223..02a66c33a 100644 --- a/src/include/storage/page/page_guard.h +++ b/src/include/storage/page/page_guard.h @@ -1,241 +1,216 @@ +//===----------------------------------------------------------------------===// +// +// BusTub +// +// page_guard.h +// +// Identification: src/include/storage/page/page_guard.h +// +// Copyright (c) 2024-2024, Carnegie Mellon University Database Group +// +//===----------------------------------------------------------------------===// + #pragma once +#include + +#include "buffer/buffer_pool_manager.h" #include "storage/page/page.h" namespace bustub { class BufferPoolManager; -class ReadPageGuard; -class WritePageGuard; +class FrameHeader; + +/** + * @brief An RAII object that grants thread-safe read access to a page of data. + * + * The _only_ way that the BusTub system should interact with the buffer pool's page data is via page guards. Since + * `ReadPageGuard` is an RAII object, the system never has to manually lock and unlock a page's latch. + * + * With `ReadPageGuard`s, there can be multiple threads that share read access to a page's data. However, the existence + * of any `ReadPageGuard` on a page implies that no thread can be mutating the page's data. + */ +class ReadPageGuard { + /** @brief Only the buffer pool manager is allowed to construct a valid `ReadPageGuard.` */ + friend class BufferPoolManager; -class BasicPageGuard { public: - BasicPageGuard() = default; + /** + * @brief The default constructor for a `ReadPageGuard`. + * + * Note that we do not EVER want use a guard that has only been default constructed. The only reason we even define + * this default constructor is to enable placing an "uninitialized" guard on the stack that we can later move assign + * via `=`. + * + * **Use of an uninitialized page guard is undefined behavior.** + * + * In other words, the only way to get a valid `ReadPageGuard` is through the buffer pool manager. + */ + ReadPageGuard() = default; - BasicPageGuard(BufferPoolManager *bpm, Page *page) : bpm_(bpm), page_(page) {} + ReadPageGuard(const ReadPageGuard &) = delete; + auto operator=(const ReadPageGuard &) -> ReadPageGuard & = delete; + ReadPageGuard(ReadPageGuard &&that) noexcept; + auto operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard &; + auto GetPageId() const -> page_id_t; + auto GetData() const -> const char *; + template + auto As() const -> const T * { + return reinterpret_cast(GetData()); + } + auto IsDirty() const -> bool; + void Drop(); + ~ReadPageGuard(); - BasicPageGuard(const BasicPageGuard &) = delete; - auto operator=(const BasicPageGuard &) -> BasicPageGuard & = delete; + private: + /** @brief Only the buffer pool manager is allowed to construct a valid `ReadPageGuard.` */ + explicit ReadPageGuard(page_id_t page_id, std::shared_ptr frame, std::shared_ptr replacer, + std::shared_ptr bpm_latch); - /** TODO(P1): Add implementation - * - * @brief Move constructor for BasicPageGuard + /** @brief The page ID of the page we are guarding. */ + page_id_t page_id_; + + /** + * @brief The frame that holds the page this guard is protecting. * - * 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. + * Almost all operations of this page guard should be done via this shared pointer to a `FrameHeader`. */ - BasicPageGuard(BasicPageGuard &&that) noexcept; + std::shared_ptr frame_; - /** TODO(P1): Add implementation - * - * @brief Drop a page guard + /** + * @brief A shared pointer to the buffer pool's replacer. * - * 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. + * Since the buffer pool cannot know when this `ReadPageGuard` gets destructed, we maintain a pointer to the buffer + * pool's replacer in order to set the frame as evictable on destruction. */ - void Drop(); + std::shared_ptr replacer_; - /** TODO(P1): Add implementation + /** + * @brief A shared pointer to the buffer pool's latch. * - * @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. + * Since the buffer pool cannot know when this `ReadPageGuard` gets destructed, we maintain a pointer to the buffer + * pool's latch for when we need to update the frame's eviction state in the buffer pool replacer. */ - auto operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard &; + std::shared_ptr bpm_latch_; - /** TODO(P1): Add implementation + /** + * @brief The validity flag for this `ReadPageGuard`. * - * @brief Destructor for BasicPageGuard + * Since we must allow for the construction of invalid page guards (see the documentation above), we must maintain + * some sort of state that tells us if this page guard is valid or not. Note that the default constructor will + * automatically set this field to `false`. * - * When a page guard goes out of scope, it should behave as if - * the page guard was dropped. + * If we did not maintain this flag, then the move constructor / move assignment operators could attempt to destruct + * or `Drop()` invalid members, causing a segmentation fault. */ - ~BasicPageGuard(); + bool is_valid_{false}; - /** TODO(P1): Add implementation - * - * @brief Upgrade a BasicPageGuard to a ReadPageGuard - * - * The protected page is not evicted from the buffer pool during the upgrade, - * and the basic page guard should be made invalid after calling this function. + /** + * TODO(P1): You may add any fields under here that you think are necessary. * - * @return an upgraded ReadPageGuard + * If you want extra (non-existent) style points, and you want to be extra fancy, then you can look into the + * `std::shared_lock` type and use that for the latching mechanism instead of manually calling `lock` and `unlock`. */ - auto UpgradeRead() -> ReadPageGuard; +}; + +/** + * @brief An RAII object that grants thread-safe write access to a page of data. + * + * The _only_ way that the BusTub system should interact with the buffer pool's page data is via page guards. Since + * `WritePageGuard` is an RAII object, the system never has to manually lock and unlock a page's latch. + * + * With a `WritePageGuard`, there can be only be 1 thread that has exclusive ownership over the page's data. This means + * that the owner of the `WritePageGuard` can mutate the page's data as much as they want. However, the existence of a + * `WritePageGuard` implies that no other `WritePageGuard` or any `ReadPageGuard`s for the same page can exist at the + * same time. + */ +class WritePageGuard { + /** @brief Only the buffer pool manager is allowed to construct a valid `WritePageGuard.` */ + friend class BufferPoolManager; - /** TODO(P1): Add implementation + public: + /** + * @brief The default constructor for a `WritePageGuard`. * - * @brief Upgrade a BasicPageGuard to a WritePageGuard + * Note that we do not EVER want use a guard that has only been default constructed. The only reason we even define + * this default constructor is to enable placing an "uninitialized" guard on the stack that we can later move assign + * via `=`. * - * The protected page is not evicted from the buffer pool during the upgrade, - * and the basic page guard should be made invalid after calling this function. + * **Use of an uninitialized page guard is undefined behavior.** * - * @return an upgraded WritePageGuard + * In other words, the only way to get a valid `WritePageGuard` is through the buffer pool manager. */ - auto UpgradeWrite() -> WritePageGuard; - - auto PageId() -> page_id_t { return page_->GetPageId(); } - - auto GetData() -> const char * { return page_->GetData(); } + WritePageGuard() = default; + WritePageGuard(const WritePageGuard &) = delete; + auto operator=(const WritePageGuard &) -> WritePageGuard & = delete; + WritePageGuard(WritePageGuard &&that) noexcept; + auto operator=(WritePageGuard &&that) noexcept -> WritePageGuard &; + auto GetPageId() const -> page_id_t; + auto GetData() const -> const char *; template - auto As() -> const T * { + auto As() const -> const T * { return reinterpret_cast(GetData()); } - - auto GetDataMut() -> char * { - is_dirty_ = true; - return page_->GetData(); - } - + auto GetDataMut() -> char *; template auto AsMut() -> T * { return reinterpret_cast(GetDataMut()); } + auto IsDirty() const -> bool; + void Drop(); + ~WritePageGuard(); private: - friend class ReadPageGuard; - friend class WritePageGuard; - - [[maybe_unused]] BufferPoolManager *bpm_{nullptr}; - Page *page_{nullptr}; - bool is_dirty_{false}; -}; - -class ReadPageGuard { - public: - ReadPageGuard() = default; - ReadPageGuard(BufferPoolManager *bpm, Page *page); - ReadPageGuard(const ReadPageGuard &) = delete; - auto operator=(const ReadPageGuard &) -> ReadPageGuard & = delete; + /** @brief Only the buffer pool manager is allowed to construct a valid `WritePageGuard.` */ + explicit WritePageGuard(page_id_t page_id, std::shared_ptr frame, std::shared_ptr replacer, + std::shared_ptr bpm_latch); - /** TODO(P1): Add implementation - * - * @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 The page ID of the page we are guarding. */ + page_id_t page_id_; - /** TODO(P1): Add implementation + /** + * @brief The frame that holds the page this guard is protecting. * - * @brief Move assignment for ReadPageGuard - * - * Very similar to BasicPageGuard. Given another ReadPageGuard, - * replace the contents of this one with that one. + * Almost all operations of this page guard should be done via this shared pointer to a `FrameHeader`. */ - auto operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard &; + std::shared_ptr frame_; - /** TODO(P1): Add implementation - * - * @brief Drop a ReadPageGuard + /** + * @brief A shared pointer to the buffer pool's replacer. * - * 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. + * Since the buffer pool cannot know when this `WritePageGuard` gets destructed, we maintain a pointer to the buffer + * pool's replacer in order to set the frame as evictable on destruction. */ - void Drop(); + std::shared_ptr replacer_; - /** TODO(P1): Add implementation - * - * @brief Destructor for ReadPageGuard + /** + * @brief A shared pointer to the buffer pool's latch. * - * Just like with BasicPageGuard, this should behave - * as if you were dropping the guard. + * Since the buffer pool cannot know when this `WritePageGuard` gets destructed, we maintain a pointer to the buffer + * pool's latch for when we need to update the frame's eviction state in the buffer pool replacer. */ - ~ReadPageGuard(); + std::shared_ptr bpm_latch_; - 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); - WritePageGuard(const WritePageGuard &) = delete; - auto operator=(const WritePageGuard &) -> WritePageGuard & = delete; - - /** TODO(P1): Add implementation + /** + * @brief The validity flag for this `WritePageGuard`. * - * @brief Move constructor for WritePageGuard + * Since we must allow for the construction of invalid page guards (see the documentation above), we must maintain + * some sort of state that tells us if this page guard is valid or not. Note that the default constructor will + * automatically set this field to `false`. * - * 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... + * If we did not maintain this flag, then the move constructor / move assignment operators could attempt to destruct + * or `Drop()` invalid members, causing a segmentation fault. */ - WritePageGuard(WritePageGuard &&that) noexcept; + bool is_valid_{false}; - /** TODO(P1): Add implementation + /** + * TODO(P1): You may add any fields under here that you think are necessary. * - * @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. + * If you want extra (non-existent) style points, and you want to be extra fancy, then you can look into the + * `std::unique_lock` type and use that for the latching mechanism instead of manually calling `lock` and `unlock`. */ - 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_; }; } // namespace bustub diff --git a/src/storage/disk/disk_manager.cpp b/src/storage/disk/disk_manager.cpp index 712a2aa82..0fb3e1cfb 100644 --- a/src/storage/disk/disk_manager.cpp +++ b/src/storage/disk/disk_manager.cpp @@ -26,11 +26,15 @@ namespace bustub { static char *buffer_used; +/** @brief The default size of the database file. */ +static const size_t DEFAULT_DB_IO_SIZE = 16; + /** * Constructor: open/create a single database file & log file * @input db_file: database file name */ -DiskManager::DiskManager(const std::filesystem::path &db_file) : file_name_(db_file) { +DiskManager::DiskManager(const std::filesystem::path &db_file) + : file_name_(db_file), pages_(0), page_capacity_(DEFAULT_DB_IO_SIZE) { log_name_ = file_name_.filename().stem().string() + ".log"; log_io_.open(log_name_, std::ios::binary | std::ios::in | std::ios::app | std::ios::out); @@ -55,6 +59,11 @@ DiskManager::DiskManager(const std::filesystem::path &db_file) : file_name_(db_f throw Exception("can't open db file"); } } + + // Initialize the database file. + std::filesystem::resize_file(db_file, (page_capacity_ + 1) * BUSTUB_PAGE_SIZE); + assert(static_cast(GetFileSize(file_name_)) >= page_capacity_ * BUSTUB_PAGE_SIZE); + buffer_used = nullptr; } @@ -69,22 +78,44 @@ void DiskManager::ShutDown() { log_io_.close(); } +/** + * @brief Increases the size of the file to fit the specified number of pages. + */ +void DiskManager::IncreaseDiskSpace(size_t pages) { + std::scoped_lock scoped_db_io_latch(db_io_latch_); + + if (pages < pages_) { + return; + } + + pages_ = pages; + while (page_capacity_ < pages_) { + page_capacity_ *= 2; + } + + std::filesystem::resize_file(file_name_, (page_capacity_ + 1) * BUSTUB_PAGE_SIZE); + + assert(static_cast(GetFileSize(file_name_)) >= page_capacity_ * BUSTUB_PAGE_SIZE); +} + /** * Write the contents of the specified page into disk file */ void DiskManager::WritePage(page_id_t page_id, const char *page_data) { std::scoped_lock scoped_db_io_latch(db_io_latch_); size_t offset = static_cast(page_id) * BUSTUB_PAGE_SIZE; - // set write cursor to offset + + // Set the write cursor to the page offset. num_writes_ += 1; db_io_.seekp(offset); db_io_.write(page_data, BUSTUB_PAGE_SIZE); - // check for I/O error + if (db_io_.bad()) { LOG_DEBUG("I/O error while writing"); return; } - // needs to flush to keep disk file in sync + + // Flush the write to disk. db_io_.flush(); } @@ -94,26 +125,29 @@ void DiskManager::WritePage(page_id_t page_id, const char *page_data) { void DiskManager::ReadPage(page_id_t page_id, char *page_data) { std::scoped_lock scoped_db_io_latch(db_io_latch_); int offset = page_id * BUSTUB_PAGE_SIZE; - // check if read beyond file length + + // Check if we have read beyond the file length. if (offset > GetFileSize(file_name_)) { - LOG_DEBUG("I/O error reading past end of file"); - // std::cerr << "I/O error while reading" << std::endl; - } else { - // set read cursor to offset - db_io_.seekp(offset); - db_io_.read(page_data, BUSTUB_PAGE_SIZE); - if (db_io_.bad()) { - LOG_DEBUG("I/O error while reading"); - return; - } - // if file ends before reading BUSTUB_PAGE_SIZE - int read_count = db_io_.gcount(); - if (read_count < BUSTUB_PAGE_SIZE) { - LOG_DEBUG("Read less than a page"); - db_io_.clear(); - // std::cerr << "Read less than a page" << std::endl; - memset(page_data + read_count, 0, BUSTUB_PAGE_SIZE - read_count); - } + LOG_DEBUG("I/O error: Read past the end of file at offset %d", offset); + return; + } + + // Set the read cursor to the page offset. + db_io_.seekg(offset); + db_io_.read(page_data, BUSTUB_PAGE_SIZE); + + if (db_io_.bad()) { + LOG_DEBUG("I/O error while reading"); + return; + } + + // Check if the file ended before we could read a full page. + int read_count = db_io_.gcount(); + if (read_count < BUSTUB_PAGE_SIZE) { + LOG_DEBUG("I/O error: Read hit the end of file at offset %d, missing %d bytes", offset, + BUSTUB_PAGE_SIZE - read_count); + db_io_.clear(); + memset(page_data + read_count, 0, BUSTUB_PAGE_SIZE - read_count); } } diff --git a/src/storage/disk/disk_manager_memory.cpp b/src/storage/disk/disk_manager_memory.cpp index b3226e79f..963b6c73e 100644 --- a/src/storage/disk/disk_manager_memory.cpp +++ b/src/storage/disk/disk_manager_memory.cpp @@ -21,13 +21,18 @@ #include "common/exception.h" #include "common/logger.h" +#include "common/macros.h" namespace bustub { /** * Constructor: used for memory based manager */ -DiskManagerMemory::DiskManagerMemory(size_t pages) { memory_ = new char[pages * BUSTUB_PAGE_SIZE]; } +DiskManagerMemory::DiskManagerMemory(size_t pages) : pages_(pages) { memory_ = new char[pages * BUSTUB_PAGE_SIZE]; } + +void DiskManagerMemory::IncreaseDiskSpace(size_t pages) { + BUSTUB_ASSERT(pages < pages_, "Ran out of disk space for limited memory disk manager implementation"); +} /** * Write the contents of the specified page into disk file diff --git a/src/storage/index/b_plus_tree.cpp b/src/storage/index/b_plus_tree.cpp index 9afdfffd5..5da9cea98 100644 --- a/src/storage/index/b_plus_tree.cpp +++ b/src/storage/index/b_plus_tree.cpp @@ -17,7 +17,7 @@ BPLUSTREE_TYPE::BPlusTree(std::string name, page_id_t header_page_id, BufferPool leaf_max_size_(leaf_max_size), internal_max_size_(internal_max_size), header_page_id_(header_page_id) { - WritePageGuard guard = bpm_->FetchPageWrite(header_page_id_); + WritePageGuard guard = bpm_->WritePage(header_page_id_); auto root_page = guard.AsMut(); root_page->root_page_id_ = INVALID_PAGE_ID; } @@ -176,8 +176,10 @@ void BPLUSTREE_TYPE::BatchOpsFromFile(const std::filesystem::path &file_name, Tr INDEX_TEMPLATE_ARGUMENTS void BPLUSTREE_TYPE::Print(BufferPoolManager *bpm) { auto root_page_id = GetRootPageId(); - auto guard = bpm->FetchPageBasic(root_page_id); - PrintTree(guard.PageId(), guard.template As()); + if (root_page_id != INVALID_PAGE_ID) { + auto guard = bpm->ReadPage(root_page_id); + PrintTree(guard.GetPageId(), guard.template As()); + } } INDEX_TEMPLATE_ARGUMENTS @@ -212,8 +214,8 @@ void BPLUSTREE_TYPE::PrintTree(page_id_t page_id, const BPlusTreePage *page) { std::cout << std::endl; std::cout << std::endl; for (int i = 0; i < internal->GetSize(); i++) { - auto guard = bpm_->FetchPageBasic(internal->ValueAt(i)); - PrintTree(guard.PageId(), guard.template As()); + auto guard = bpm_->ReadPage(internal->ValueAt(i)); + PrintTree(guard.GetPageId(), guard.template As()); } } } @@ -231,8 +233,8 @@ void BPLUSTREE_TYPE::Draw(BufferPoolManager *bpm, const std::filesystem::path &o std::ofstream out(outf); out << "digraph G {" << std::endl; auto root_page_id = GetRootPageId(); - auto guard = bpm->FetchPageBasic(root_page_id); - ToGraph(guard.PageId(), guard.template As(), out); + auto guard = bpm->ReadPage(root_page_id); + ToGraph(guard.GetPageId(), guard.template As(), out); out << "}" << std::endl; out.close(); } @@ -297,22 +299,22 @@ void BPLUSTREE_TYPE::ToGraph(page_id_t page_id, const BPlusTreePage *page, std:: out << ">];\n"; // Print leaves for (int i = 0; i < inner->GetSize(); i++) { - auto child_guard = bpm_->FetchPageBasic(inner->ValueAt(i)); + auto child_guard = bpm_->ReadPage(inner->ValueAt(i)); auto child_page = child_guard.template As(); - ToGraph(child_guard.PageId(), child_page, out); + ToGraph(child_guard.GetPageId(), child_page, out); if (i > 0) { - auto sibling_guard = bpm_->FetchPageBasic(inner->ValueAt(i - 1)); + auto sibling_guard = bpm_->ReadPage(inner->ValueAt(i - 1)); auto sibling_page = sibling_guard.template As(); if (!sibling_page->IsLeafPage() && !child_page->IsLeafPage()) { - out << "{rank=same " << internal_prefix << sibling_guard.PageId() << " " << internal_prefix - << child_guard.PageId() << "};\n"; + out << "{rank=same " << internal_prefix << sibling_guard.GetPageId() << " " << internal_prefix + << child_guard.GetPageId() << "};\n"; } } - out << internal_prefix << page_id << ":p" << child_guard.PageId() << " -> "; + out << internal_prefix << page_id << ":p" << child_guard.GetPageId() << " -> "; if (child_page->IsLeafPage()) { - out << leaf_prefix << child_guard.PageId() << ";\n"; + out << leaf_prefix << child_guard.GetPageId() << ";\n"; } else { - out << internal_prefix << child_guard.PageId() << ";\n"; + out << internal_prefix << child_guard.GetPageId() << ";\n"; } } } @@ -333,7 +335,7 @@ auto BPLUSTREE_TYPE::DrawBPlusTree() -> std::string { INDEX_TEMPLATE_ARGUMENTS auto BPLUSTREE_TYPE::ToPrintableBPlusTree(page_id_t root_id) -> PrintableBPlusTree { - auto root_page_guard = bpm_->FetchPageBasic(root_id); + auto root_page_guard = bpm_->ReadPage(root_id); auto root_page = root_page_guard.template As(); PrintableBPlusTree proot; diff --git a/src/storage/index/b_plus_tree_index.cpp b/src/storage/index/b_plus_tree_index.cpp index b4cefb9fb..5b613729d 100644 --- a/src/storage/index/b_plus_tree_index.cpp +++ b/src/storage/index/b_plus_tree_index.cpp @@ -18,8 +18,7 @@ namespace bustub { INDEX_TEMPLATE_ARGUMENTS BPLUSTREE_INDEX_TYPE::BPlusTreeIndex(std::unique_ptr &&metadata, BufferPoolManager *buffer_pool_manager) : Index(std::move(metadata)), comparator_(GetMetadata()->GetKeySchema()) { - page_id_t header_page_id; - buffer_pool_manager->NewPage(&header_page_id); + page_id_t header_page_id = buffer_pool_manager->NewPage(); container_ = std::make_shared>(GetMetadata()->GetName(), header_page_id, buffer_pool_manager, comparator_); } diff --git a/src/storage/page/page_guard.cpp b/src/storage/page/page_guard.cpp index 3ee79fd76..a7cf23d8d 100644 --- a/src/storage/page/page_guard.cpp +++ b/src/storage/page/page_guard.cpp @@ -1,38 +1,217 @@ +//===----------------------------------------------------------------------===// +// +// BusTub +// +// page_guard.cpp +// +// Identification: src/storage/page/page_guard.cpp +// +// Copyright (c) 2024-2024, Carnegie Mellon University Database Group +// +//===----------------------------------------------------------------------===// + #include "storage/page/page_guard.h" -#include "buffer/buffer_pool_manager.h" namespace bustub { -BasicPageGuard::BasicPageGuard(BasicPageGuard &&that) noexcept {} - -void BasicPageGuard::Drop() {} - -auto BasicPageGuard::operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard & { return *this; } - -BasicPageGuard::~BasicPageGuard(){}; // NOLINT - -auto BasicPageGuard::UpgradeRead() -> ReadPageGuard { return {bpm_, page_}; } - -auto BasicPageGuard::UpgradeWrite() -> WritePageGuard { return {bpm_, page_}; } - -ReadPageGuard::ReadPageGuard(BufferPoolManager *bpm, Page *page) {} - -ReadPageGuard::ReadPageGuard(ReadPageGuard &&that) noexcept = default; - +/** + * @brief The only constructor for an RAII `ReadPageGuard` that creates a valid guard. + * + * Note that only the buffer pool manager is allowed to call this constructor. + * + * TODO(P1): Add implementation. + * + * @param page_id The page ID of the page we want to read. + * @param frame A shared pointer to the frame that holds the page we want to protect. + * @param replacer A shared pointer to the buffer pool manager's replacer. + * @param bpm_latch A shared pointer to the buffer pool manager's latch. + */ +ReadPageGuard::ReadPageGuard(page_id_t page_id, std::shared_ptr frame, + std::shared_ptr replacer, std::shared_ptr bpm_latch) + : page_id_(page_id), frame_(std::move(frame)), replacer_(std::move(replacer)), bpm_latch_(std::move(bpm_latch)) { + UNIMPLEMENTED("TODO(P1): Add implementation."); +} + +/** + * @brief The move constructor for `ReadPageGuard`. + * + * ### Implementation + * + * If you are unfamiliar with move semantics, please familiarize yourself with learning materials online. There are many + * great resources (including articles, Microsoft tutorials, YouTube videos) that explain this in depth. + * + * Make sure you invalidate the other guard, otherwise you might run into double free problems! For both objects, you + * need to update _at least_ 5 fields each. + * + * TODO(P1): Add implementation. + * + * @param that The other page guard. + */ +ReadPageGuard::ReadPageGuard(ReadPageGuard &&that) noexcept {} + +/** + * @brief The move assignment operator for `ReadPageGuard`. + * + * ### Implementation + * + * If you are unfamiliar with move semantics, please familiarize yourself with learning materials online. There are many + * great resources (including articles, Microsoft tutorials, YouTube videos) that explain this in depth. + * + * Make sure you invalidate the other guard, otherwise you might run into double free problems! For both objects, you + * need to update _at least_ 5 fields each, and for the current object, make sure you release any resources it might be + * holding on to. + * + * TODO(P1): Add implementation. + * + * @param that The other page guard. + * @return ReadPageGuard& The newly valid `ReadPageGuard`. + */ auto ReadPageGuard::operator=(ReadPageGuard &&that) noexcept -> ReadPageGuard & { return *this; } -void ReadPageGuard::Drop() {} - -ReadPageGuard::~ReadPageGuard() {} // NOLINT - -WritePageGuard::WritePageGuard(BufferPoolManager *bpm, Page *page) {} - -WritePageGuard::WritePageGuard(WritePageGuard &&that) noexcept = default; - +/** + * @brief Gets the page ID of the page this guard is protecting. + */ +auto ReadPageGuard::GetPageId() const -> page_id_t { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid read guard"); + return page_id_; +} + +/** + * @brief Gets a `const` pointer to the page of data this guard is protecting. + */ +auto ReadPageGuard::GetData() const -> const char * { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid read guard"); + return frame_->GetData(); +} + +/** + * @brief Returns whether the page is dirty (modified but not flushed to the disk). + */ +auto ReadPageGuard::IsDirty() const -> bool { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid read guard"); + return frame_->is_dirty_; +} + +/** + * @brief Manually drops a valid `ReadPageGuard`'s data. If this guard is invalid, this function does nothing. + * + * ### Implementation + * + * Make sure you don't double free! Also, think **very** **VERY** carefully about what resources you own and the order + * in which you release those resources. If you get the ordering wrong, you will very likely fail one of the later + * Gradescope tests. You may also want to take the buffer pool manager's latch in a very specific scenario... + * + * TODO(P1): Add implementation. + */ +void ReadPageGuard::Drop() { UNIMPLEMENTED("TODO(P1): Add implementation."); } + +/** @brief The destructor for `ReadPageGuard`. This destructor simply calls `Drop()`. */ +ReadPageGuard::~ReadPageGuard() { Drop(); } + +/**********************************************************************************************************************/ +/**********************************************************************************************************************/ +/**********************************************************************************************************************/ + +/** + * @brief The only constructor for an RAII `WritePageGuard` that creates a valid guard. + * + * Note that only the buffer pool manager is allowed to call this constructor. + * + * TODO(P1): Add implementation. + * + * @param page_id The page ID of the page we want to write to. + * @param frame A shared pointer to the frame that holds the page we want to protect. + * @param replacer A shared pointer to the buffer pool manager's replacer. + * @param bpm_latch A shared pointer to the buffer pool manager's latch. + */ +WritePageGuard::WritePageGuard(page_id_t page_id, std::shared_ptr frame, + std::shared_ptr replacer, std::shared_ptr bpm_latch) + : page_id_(page_id), frame_(std::move(frame)), replacer_(std::move(replacer)), bpm_latch_(std::move(bpm_latch)) { + UNIMPLEMENTED("TODO(P1): Add implementation."); +} + +/** + * @brief The move constructor for `WritePageGuard`. + * + * ### Implementation + * + * If you are unfamiliar with move semantics, please familiarize yourself with learning materials online. There are many + * great resources (including articles, Microsoft tutorials, YouTube videos) that explain this in depth. + * + * Make sure you invalidate the other guard, otherwise you might run into double free problems! For both objects, you + * need to update _at least_ 5 fields each. + * + * TODO(P1): Add implementation. + * + * @param that The other page guard. + */ +WritePageGuard::WritePageGuard(WritePageGuard &&that) noexcept {} + +/** + * @brief The move assignment operator for `WritePageGuard`. + * + * ### Implementation + * + * If you are unfamiliar with move semantics, please familiarize yourself with learning materials online. There are many + * great resources (including articles, Microsoft tutorials, YouTube videos) that explain this in depth. + * + * Make sure you invalidate the other guard, otherwise you might run into double free problems! For both objects, you + * need to update _at least_ 5 fields each, and for the current object, make sure you release any resources it might be + * holding on to. + * + * TODO(P1): Add implementation. + * + * @param that The other page guard. + * @return WritePageGuard& The newly valid `WritePageGuard`. + */ auto WritePageGuard::operator=(WritePageGuard &&that) noexcept -> WritePageGuard & { return *this; } -void WritePageGuard::Drop() {} - -WritePageGuard::~WritePageGuard() {} // NOLINT +/** + * @brief Gets the page ID of the page this guard is protecting. + */ +auto WritePageGuard::GetPageId() const -> page_id_t { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid write guard"); + return page_id_; +} + +/** + * @brief Gets a `const` pointer to the page of data this guard is protecting. + */ +auto WritePageGuard::GetData() const -> const char * { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid write guard"); + return frame_->GetData(); +} + +/** + * @brief Gets a mutable pointer to the page of data this guard is protecting. + */ +auto WritePageGuard::GetDataMut() -> char * { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid write guard"); + return frame_->GetDataMut(); +} + +/** + * @brief Returns whether the page is dirty (modified but not flushed to the disk). + */ +auto WritePageGuard::IsDirty() const -> bool { + BUSTUB_ENSURE(is_valid_, "tried to use an invalid write guard"); + return frame_->is_dirty_; +} + +/** + * @brief Manually drops a valid `WritePageGuard`'s data. If this guard is invalid, this function does nothing. + * + * ### Implementation + * + * Make sure you don't double free! Also, think **very** **VERY** carefully about what resources you own and the order + * in which you release those resources. If you get the ordering wrong, you will very likely fail one of the later + * Gradescope tests. You may also want to take the buffer pool manager's latch in a very specific scenario... + * + * TODO(P1): Add implementation. + */ +void WritePageGuard::Drop() { UNIMPLEMENTED("TODO(P1): Add implementation."); } + +/** @brief The destructor for `WritePageGuard`. This destructor simply calls `Drop()`. */ +WritePageGuard::~WritePageGuard() { Drop(); } } // namespace bustub diff --git a/src/storage/table/table_heap.cpp b/src/storage/table/table_heap.cpp index 5aa226b1f..59005e044 100644 --- a/src/storage/table/table_heap.cpp +++ b/src/storage/table/table_heap.cpp @@ -28,11 +28,14 @@ namespace bustub { TableHeap::TableHeap(BufferPoolManager *bpm) : bpm_(bpm) { // Initialize the first table page. - auto guard = bpm->NewPageGuarded(&first_page_id_); + first_page_id_ = bpm->NewPage(); last_page_id_ = first_page_id_; + + auto guard = bpm->WritePage(first_page_id_); auto first_page = guard.AsMut(); BUSTUB_ASSERT(first_page != nullptr, "Couldn't create a page for the table heap. Have you completed the buffer pool manager project?"); + first_page->Init(); } @@ -41,7 +44,8 @@ TableHeap::TableHeap(bool create_table_heap) : bpm_(nullptr) {} auto TableHeap::InsertTuple(const TupleMeta &meta, const Tuple &tuple, LockManager *lock_mgr, Transaction *txn, table_oid_t oid) -> std::optional { std::unique_lock guard(latch_); - auto page_guard = bpm_->FetchPageWrite(last_page_id_); + auto page_guard = bpm_->WritePage(last_page_id_); + while (true) { auto page = page_guard.AsMut(); if (page->GetNextTupleOffset(meta, tuple) != std::nullopt) { @@ -51,22 +55,18 @@ auto TableHeap::InsertTuple(const TupleMeta &meta, const Tuple &tuple, LockManag // if there's no tuple in the page, and we can't insert the tuple, then this tuple is too large. BUSTUB_ENSURE(page->GetNumTuples() != 0, "tuple is too large, cannot insert"); - page_id_t next_page_id = INVALID_PAGE_ID; - auto npg = bpm_->NewPage(&next_page_id); - BUSTUB_ENSURE(next_page_id != INVALID_PAGE_ID, "cannot allocate page"); - + page_id_t next_page_id = bpm_->NewPage(); page->SetNextPageId(next_page_id); - auto next_page = reinterpret_cast(npg->GetData()); + auto next_page_guard = bpm_->WritePage(next_page_id); + auto next_page = next_page_guard.AsMut(); next_page->Init(); + last_page_id_ = next_page_id; page_guard.Drop(); - - auto next_page_guard = WritePageGuard{bpm_, npg}; - - last_page_id_ = next_page_id; page_guard = std::move(next_page_guard); } + auto last_page_id = last_page_id_; auto page = page_guard.AsMut(); @@ -88,13 +88,13 @@ auto TableHeap::InsertTuple(const TupleMeta &meta, const Tuple &tuple, LockManag } void TableHeap::UpdateTupleMeta(const TupleMeta &meta, RID rid) { - auto page_guard = bpm_->FetchPageWrite(rid.GetPageId()); + auto page_guard = bpm_->WritePage(rid.GetPageId()); auto page = page_guard.AsMut(); page->UpdateTupleMeta(meta, rid); } auto TableHeap::GetTuple(RID rid) -> std::pair { - auto page_guard = bpm_->FetchPageRead(rid.GetPageId()); + auto page_guard = bpm_->ReadPage(rid.GetPageId()); auto page = page_guard.As(); auto [meta, tuple] = page->GetTuple(rid); tuple.rid_ = rid; @@ -102,7 +102,7 @@ auto TableHeap::GetTuple(RID rid) -> std::pair { } auto TableHeap::GetTupleMeta(RID rid) -> TupleMeta { - auto page_guard = bpm_->FetchPageRead(rid.GetPageId()); + auto page_guard = bpm_->ReadPage(rid.GetPageId()); auto page = page_guard.As(); return page->GetTupleMeta(rid); } @@ -112,7 +112,7 @@ auto TableHeap::MakeIterator() -> TableIterator { auto last_page_id = last_page_id_; guard.unlock(); - auto page_guard = bpm_->FetchPageRead(last_page_id); + auto page_guard = bpm_->ReadPage(last_page_id); auto page = page_guard.As(); auto num_tuples = page->GetNumTuples(); page_guard.Drop(); @@ -124,7 +124,7 @@ auto TableHeap::MakeEagerIterator() -> TableIterator { return {this, {first_page auto TableHeap::UpdateTupleInPlace(const TupleMeta &meta, const Tuple &tuple, RID rid, std::function &&check) -> bool { - auto page_guard = bpm_->FetchPageWrite(rid.GetPageId()); + auto page_guard = bpm_->WritePage(rid.GetPageId()); auto page = page_guard.AsMut(); auto [old_meta, old_tup] = page->GetTuple(rid); if (check == nullptr || check(old_meta, old_tup, rid)) { @@ -134,9 +134,9 @@ auto TableHeap::UpdateTupleInPlace(const TupleMeta &meta, const Tuple &tuple, RI return false; } -auto TableHeap::AcquireTablePageReadLock(RID rid) -> ReadPageGuard { return bpm_->FetchPageRead(rid.GetPageId()); } +auto TableHeap::AcquireTablePageReadLock(RID rid) -> ReadPageGuard { return bpm_->ReadPage(rid.GetPageId()); } -auto TableHeap::AcquireTablePageWriteLock(RID rid) -> WritePageGuard { return bpm_->FetchPageWrite(rid.GetPageId()); } +auto TableHeap::AcquireTablePageWriteLock(RID rid) -> WritePageGuard { return bpm_->WritePage(rid.GetPageId()); } void TableHeap::UpdateTupleInPlaceWithLockAcquired(const TupleMeta &meta, const Tuple &tuple, RID rid, TablePage *page) { diff --git a/src/storage/table/table_iterator.cpp b/src/storage/table/table_iterator.cpp index 7d69b7843..c6839b231 100644 --- a/src/storage/table/table_iterator.cpp +++ b/src/storage/table/table_iterator.cpp @@ -27,7 +27,7 @@ TableIterator::TableIterator(TableHeap *table_heap, RID rid, RID stop_at_rid) if (rid.GetPageId() == INVALID_PAGE_ID) { rid_ = RID{INVALID_PAGE_ID, 0}; } else { - auto page_guard = table_heap_->bpm_->FetchPageRead(rid_.GetPageId()); + auto page_guard = table_heap_->bpm_->ReadPage(rid_.GetPageId()); auto page = page_guard.As(); if (rid_.GetSlotNum() >= page->GetNumTuples()) { rid_ = RID{INVALID_PAGE_ID, 0}; @@ -42,7 +42,7 @@ auto TableIterator::GetRID() -> RID { return rid_; } auto TableIterator::IsEnd() -> bool { return rid_.GetPageId() == INVALID_PAGE_ID; } auto TableIterator::operator++() -> TableIterator & { - auto page_guard = table_heap_->bpm_->FetchPageRead(rid_.GetPageId()); + auto page_guard = table_heap_->bpm_->ReadPage(rid_.GetPageId()); auto page = page_guard.As(); auto next_tuple_id = rid_.GetSlotNum() + 1; diff --git a/test/buffer/buffer_pool_manager_test.cpp b/test/buffer/buffer_pool_manager_test.cpp index 76f2f3132..120dffcf3 100644 --- a/test/buffer/buffer_pool_manager_test.cpp +++ b/test/buffer/buffer_pool_manager_test.cpp @@ -6,153 +6,274 @@ // // Identification: test/buffer/buffer_pool_manager_test.cpp // -// Copyright (c) 2015-2021, Carnegie Mellon University Database Group +// Copyright (c) 2015-2024, Carnegie Mellon University Database Group // //===----------------------------------------------------------------------===// -#include "buffer/buffer_pool_manager.h" - #include +#include #include -#include -#include -#include +#include "buffer/buffer_pool_manager.h" #include "gtest/gtest.h" +#include "storage/page/page_guard.h" namespace bustub { static std::filesystem::path db_fname("test.bustub"); -// NOLINTNEXTLINE -// Check whether pages containing terminal characters can be recovered -TEST(BufferPoolManagerTest, DISABLED_BinaryDataTest) { - const size_t buffer_pool_size = 10; - const size_t k = 5; +// The number of frames we give to the buffer pool. +const size_t FRAMES = 10; +// Note that this test assumes you are using the an LRU-K replacement policy. +const size_t K_DIST = 5; - std::random_device r; - std::default_random_engine rng(r()); +TEST(BufferPoolManagerTest, DISABLED_VeryBasicTest) { + // A very basic test. - constexpr int lower_bound = static_cast(std::numeric_limits::min()); - constexpr int upper_bound = static_cast(std::numeric_limits::max()); - // No matter if `char` is signed or unsigned by default, this constraint must be met - static_assert(upper_bound - lower_bound == 255); - std::uniform_int_distribution uniform_dist(lower_bound, upper_bound); + auto disk_manager = std::make_shared(db_fname); + auto bpm = std::make_shared(FRAMES, disk_manager.get(), K_DIST); - auto *disk_manager = new DiskManager(db_fname); - auto *bpm = new BufferPoolManager(buffer_pool_size, disk_manager, k); + page_id_t pid = bpm->NewPage(); - page_id_t page_id_temp; - auto *page0 = bpm->NewPage(&page_id_temp); + char str[] = "Hello, world!"; - // Scenario: The buffer pool is empty. We should be able to create a new page. - ASSERT_NE(nullptr, page0); - EXPECT_EQ(0, page_id_temp); + // Check `WritePageGuard` basic functionality. + { + auto guard = bpm->WritePage(pid); + char *data = guard.GetDataMut(); + snprintf(data, sizeof(str), "%s", str); + EXPECT_STREQ(data, str); + } - char random_binary_data[BUSTUB_PAGE_SIZE]; - // Generate random binary data - for (char &i : random_binary_data) { - i = static_cast(uniform_dist(rng)); + // Check `ReadPageGuard` basic functionality. + { + auto guard = bpm->ReadPage(pid); + const char *data = guard.GetData(); + EXPECT_STREQ(data, str); } - // Insert terminal characters both in the middle and at end - random_binary_data[BUSTUB_PAGE_SIZE / 2] = '\0'; - random_binary_data[BUSTUB_PAGE_SIZE - 1] = '\0'; + // Check `ReadPageGuard` basic functionality (again). + { + auto guard = bpm->ReadPage(pid); + const char *data = guard.GetData(); + EXPECT_STREQ(data, str); + } - // Scenario: Once we have a page, we should be able to read and write content. - std::memcpy(page0->GetData(), random_binary_data, BUSTUB_PAGE_SIZE); - EXPECT_EQ(0, std::memcmp(page0->GetData(), random_binary_data, BUSTUB_PAGE_SIZE)); + ASSERT_TRUE(bpm->DeletePage(pid)); +} - // Scenario: We should be able to create new pages until we fill up the buffer pool. - for (size_t i = 1; i < buffer_pool_size; ++i) { - EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp)); - } +TEST(BufferPoolManagerTest, DISABLED_PagePinEasyTest) { + auto disk_manager = std::make_shared(db_fname); + auto bpm = std::make_shared(2, disk_manager.get(), 5); - // Scenario: Once the buffer pool is full, we should not be able to create any new pages. - for (size_t i = buffer_pool_size; i < buffer_pool_size * 2; ++i) { - EXPECT_EQ(nullptr, bpm->NewPage(&page_id_temp)); + page_id_t pageid0; + page_id_t pageid1; + + { + pageid0 = bpm->NewPage(); + auto page0_write_opt = bpm->CheckedWritePage(pageid0); + ASSERT_TRUE(page0_write_opt.has_value()); + WritePageGuard page0_write = std::move(page0_write_opt.value()); + strcpy(page0_write.GetDataMut(), "page0"); // NOLINT + + pageid1 = bpm->NewPage(); + auto page1_write_opt = bpm->CheckedWritePage(pageid1); + ASSERT_TRUE(page1_write_opt.has_value()); + WritePageGuard page1_write = std::move(page1_write_opt.value()); + strcpy(page1_write.GetDataMut(), "page1"); // NOLINT + + ASSERT_EQ(1, bpm->GetPinCount(pageid0)); + ASSERT_EQ(1, bpm->GetPinCount(pageid1)); + + page_id_t temp_page_id1 = bpm->NewPage(); + auto temp_page1_opt = bpm->CheckedReadPage(temp_page_id1); + ASSERT_FALSE(temp_page1_opt.has_value()); + + page_id_t temp_page_id2 = bpm->NewPage(); + auto temp_page2_opt = bpm->CheckedWritePage(temp_page_id2); + ASSERT_FALSE(temp_page2_opt.has_value()); + + ASSERT_EQ(1, bpm->GetPinCount(pageid0)); + page0_write.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pageid0)); + + ASSERT_EQ(1, bpm->GetPinCount(pageid1)); + page1_write.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pageid0)); } - // Scenario: After unpinning pages {0, 1, 2, 3, 4}, we should be able to create 5 new pages - for (int i = 0; i < 5; ++i) { - EXPECT_EQ(true, bpm->UnpinPage(i, true)); - bpm->FlushPage(i); + { + page_id_t temp_page_id1 = bpm->NewPage(); + auto temp_page1_opt = bpm->CheckedReadPage(temp_page_id1); + ASSERT_TRUE(temp_page1_opt.has_value()); + + page_id_t temp_page_id2 = bpm->NewPage(); + auto temp_page2_opt = bpm->CheckedWritePage(temp_page_id2); + ASSERT_TRUE(temp_page2_opt.has_value()); + + ASSERT_FALSE(bpm->GetPinCount(pageid0).has_value()); + ASSERT_FALSE(bpm->GetPinCount(pageid1).has_value()); } - for (int i = 0; i < 5; ++i) { - EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp)); - // Unpin the page here to allow future fetching - bpm->UnpinPage(page_id_temp, false); + + { + auto page0_write_opt = bpm->CheckedWritePage(pageid0); + ASSERT_TRUE(page0_write_opt.has_value()); + WritePageGuard page0_write = std::move(page0_write_opt.value()); + ASSERT_EQ(0, strcmp(page0_write.GetData(), "page0")); + strcpy(page0_write.GetDataMut(), "page0updated"); // NOLINT + + auto page1_write_opt = bpm->CheckedWritePage(pageid1); + ASSERT_TRUE(page1_write_opt.has_value()); + WritePageGuard page1_write = std::move(page1_write_opt.value()); + ASSERT_EQ(0, strcmp(page1_write.GetData(), "page1")); + strcpy(page1_write.GetDataMut(), "page1updated"); // NOLINT + + ASSERT_EQ(1, bpm->GetPinCount(pageid0)); + ASSERT_EQ(1, bpm->GetPinCount(pageid1)); } - // Scenario: We should be able to fetch the data we wrote a while ago. - page0 = bpm->FetchPage(0); - ASSERT_NE(nullptr, page0); - EXPECT_EQ(0, memcmp(page0->GetData(), random_binary_data, BUSTUB_PAGE_SIZE)); - EXPECT_EQ(true, bpm->UnpinPage(0, true)); + ASSERT_EQ(0, bpm->GetPinCount(pageid0)); + ASSERT_EQ(0, bpm->GetPinCount(pageid1)); - // Shutdown the disk manager and remove the temporary file we created. - disk_manager->ShutDown(); - remove(db_fname); + { + auto page0_read_opt = bpm->CheckedReadPage(pageid0); + ASSERT_TRUE(page0_read_opt.has_value()); + ReadPageGuard page0_read = std::move(page0_read_opt.value()); + ASSERT_EQ(0, strcmp(page0_read.GetData(), "page0updated")); - delete bpm; - delete disk_manager; -} + auto page1_read_opt = bpm->CheckedReadPage(pageid1); + ASSERT_TRUE(page1_read_opt.has_value()); + ReadPageGuard page1_read = std::move(page1_read_opt.value()); + ASSERT_EQ(0, strcmp(page1_read.GetData(), "page1updated")); + + ASSERT_EQ(1, bpm->GetPinCount(pageid0)); + ASSERT_EQ(1, bpm->GetPinCount(pageid1)); + } -// NOLINTNEXTLINE -TEST(BufferPoolManagerTest, DISABLED_SampleTest) { - const size_t buffer_pool_size = 10; - const size_t k = 5; + ASSERT_EQ(0, bpm->GetPinCount(pageid0)); + ASSERT_EQ(0, bpm->GetPinCount(pageid1)); - auto *disk_manager = new DiskManager(db_fname); - auto *bpm = new BufferPoolManager(buffer_pool_size, disk_manager, k); + remove(db_fname); + remove(disk_manager->GetLogFileName()); +} - page_id_t page_id_temp; - auto *page0 = bpm->NewPage(&page_id_temp); +TEST(BufferPoolManagerTest, DISABLED_PagePinMediumTest) { + auto disk_manager = std::make_shared(db_fname); + auto bpm = std::make_shared(FRAMES, disk_manager.get(), K_DIST); // Scenario: The buffer pool is empty. We should be able to create a new page. - ASSERT_NE(nullptr, page0); - EXPECT_EQ(0, page_id_temp); + page_id_t pid0 = bpm->NewPage(); + auto page0 = bpm->WritePage(pid0); // Scenario: Once we have a page, we should be able to read and write content. - snprintf(page0->GetData(), BUSTUB_PAGE_SIZE, "Hello"); - EXPECT_EQ(0, strcmp(page0->GetData(), "Hello")); + snprintf(page0.GetDataMut(), BUSTUB_PAGE_SIZE, "Hello"); + EXPECT_EQ(0, strcmp(page0.GetDataMut(), "Hello")); + + // Create a vector of unique pointers to page guards, which prevents the guards from getting destructed. + std::deque pages; + pages.push_back(std::move(page0)); // Scenario: We should be able to create new pages until we fill up the buffer pool. - for (size_t i = 1; i < buffer_pool_size; ++i) { - EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp)); + for (size_t i = 1; i < FRAMES; i++) { + auto pid = bpm->NewPage(); + auto page = bpm->WritePage(pid); + pages.push_back(std::move(page)); + } + + // Scenario: All of the pin counts should be 1. + for (const auto &page : pages) { + auto pid = page.GetPageId(); + EXPECT_EQ(1, bpm->GetPinCount(pid)); } // Scenario: Once the buffer pool is full, we should not be able to create any new pages. - for (size_t i = buffer_pool_size; i < buffer_pool_size * 2; ++i) { - EXPECT_EQ(nullptr, bpm->NewPage(&page_id_temp)); + for (size_t i = 0; i < 10; i++) { + auto pid = bpm->NewPage(); + auto fail = bpm->CheckedWritePage(pid); + ASSERT_FALSE(fail.has_value()); } - // Scenario: After unpinning pages {0, 1, 2, 3, 4} and pinning another 4 new pages, - // there would still be one buffer page left for reading page 0. - for (int i = 0; i < 5; ++i) { - EXPECT_EQ(true, bpm->UnpinPage(i, true)); + // Scenario: Drop the first 5 pages to unpin them. + for (size_t i = 0; i < 5; i++) { + page_id_t pid = pages[0].GetPageId(); + EXPECT_EQ(1, bpm->GetPinCount(pid)); + pages.pop_front(); + EXPECT_EQ(0, bpm->GetPinCount(pid)); } - for (int i = 0; i < 4; ++i) { - EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp)); + + // Scenario: All of the pin counts of the pages we haven't dropped yet should still be 1. + for (const auto &page : pages) { + auto pid = page.GetPageId(); + EXPECT_EQ(1, bpm->GetPinCount(pid)); } - // Scenario: We should be able to fetch the data we wrote a while ago. - page0 = bpm->FetchPage(0); - ASSERT_NE(nullptr, page0); - EXPECT_EQ(0, strcmp(page0->GetData(), "Hello")); + // Scenario: After unpinning pages {0, 1, 2, 3, 4}, we should be able to create 4 new pages and bring them into + // memory. Bringing those 4 pages into memory should evict the first 4 pages {0, 1, 2, 3} because of LRU. + for (size_t i = 0; i < 4; i++) { + auto pid = bpm->NewPage(); + auto page = bpm->WritePage(pid); + pages.push_back(std::move(page)); + } - // Scenario: If we unpin page 0 and then make a new page, all the buffer pages should - // now be pinned. Fetching page 0 again should fail. - EXPECT_EQ(true, bpm->UnpinPage(0, true)); - EXPECT_NE(nullptr, bpm->NewPage(&page_id_temp)); - EXPECT_EQ(nullptr, bpm->FetchPage(0)); + // Scenario: There should be one frame available, and we should be able to fetch the data we wrote a while ago. + { + ReadPageGuard original_page = bpm->ReadPage(pid0); + EXPECT_EQ(0, strcmp(original_page.GetData(), "Hello")); + } + + // Scenario: Once we unpin page 0 and then make a new page, all the buffer pages should now be pinned. Fetching page 0 + // again should fail. + auto last_pid = bpm->NewPage(); + auto last_page = bpm->ReadPage(last_pid); + + auto fail = bpm->CheckedReadPage(pid0); + ASSERT_FALSE(fail.has_value()); // Shutdown the disk manager and remove the temporary file we created. disk_manager->ShutDown(); remove(db_fname); +} - delete bpm; - delete disk_manager; +TEST(BufferPoolManagerTest, DISABLED_ContentionTest) { + auto disk_manager = std::make_shared(db_fname); + auto bpm = std::make_shared(FRAMES, disk_manager.get(), K_DIST); + + const size_t rounds = 100000; + + auto pid = bpm->NewPage(); + + auto thread1 = std::thread([&]() { + for (size_t i = 0; i < rounds; i++) { + auto guard = bpm->WritePage(pid); + strcpy(guard.GetDataMut(), std::to_string(i).c_str()); // NOLINT + } + }); + + auto thread2 = std::thread([&]() { + for (size_t i = 0; i > rounds; i++) { + auto guard = bpm->WritePage(pid); + strcpy(guard.GetDataMut(), std::to_string(i).c_str()); // NOLINT + } + }); + + auto thread3 = std::thread([&]() { + for (size_t i = 0; i > rounds; i++) { + auto guard = bpm->WritePage(pid); + strcpy(guard.GetDataMut(), std::to_string(i).c_str()); // NOLINT + } + }); + + auto thread4 = std::thread([&]() { + for (size_t i = 0; i > rounds; i++) { + auto guard = bpm->WritePage(pid); + strcpy(guard.GetDataMut(), std::to_string(i).c_str()); // NOLINT + } + }); + + thread3.join(); + thread2.join(); + thread4.join(); + thread1.join(); } } // namespace bustub diff --git a/test/buffer/counter.h b/test/buffer/counter.h deleted file mode 100644 index f62c05efb..000000000 --- a/test/buffer/counter.h +++ /dev/null @@ -1,90 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// BusTub -// -// counter.h -// -// Identification: test/buffer/counter.h -// -// Copyright (c) 2015-2019, Carnegie Mellon University Database Group -// -//===----------------------------------------------------------------------===// - -#pragma once - -#include - -#include "gtest/gtest.h" - -namespace bustub { - -enum FuncType { FetchPage, UnpinPage, FlushPage, NewPage, DeletePage, FlushAllPages }; - -struct Counter { - // 0-FetchPage 1-UnpinPage 2-FlushPage 3-NewPage 4-DeletePage - // 5-FlushAllPages - static const int num_types = 6; - std::atomic_int counts[num_types]; - - void Reset() { - for (auto &count : counts) { - count = 0; - } - } - - void AddCount(FuncType func_type) { ++counts[func_type]; } - - // Make sure fetch page function only calls fetch page once and - // does not call other functions - void CheckFetchPage() { - EXPECT_EQ(counts[0], 1) << "has to call FetchPage once"; - for (int i = 1; i < num_types; ++i) { - EXPECT_EQ(counts[i], 0) << "FetchPage Should not call other functions"; - } - } - - void CheckUnpinPage() { - EXPECT_EQ(counts[1], 1) << "has to call UnpinPage once"; - for (int i = 0; i < num_types; ++i) { - if (i != 1) { - EXPECT_EQ(counts[i], 0) << "UnPinPage Should not call other functions"; - } - } - } - - void CheckFlushPage() { - EXPECT_EQ(counts[2], 1) << "has to call FlushPage once"; - for (int i = 0; i < num_types; ++i) { - if (i != 2) { - EXPECT_EQ(counts[i], 0) << "FlushPage Should not call other functions"; - } - } - } - - void CheckNewPage() { - EXPECT_EQ(counts[3], 1) << "has to call NewPage once"; - for (int i = 0; i < num_types; ++i) { - if (i != 3) { - EXPECT_EQ(counts[i], 0) << "NewPage Should not call other functions"; - } - } - } - - void CheckDeletePage() { - EXPECT_EQ(counts[4], 1) << "has to call DeletePage once"; - for (int i = 0; i < num_types; ++i) { - if (i != 4) { - EXPECT_EQ(counts[i], 0) << "DeletePage Should not call other functions"; - } - } - } - - void CheckFlushAllPages() { - for (int i = 1; i < 5; ++i) { - EXPECT_EQ(counts[i], 0) << "FlushAllPage Should not call other functions"; - } - EXPECT_EQ(counts[5], 1) << "has to call FlushAllPage once"; - } -}; - -} // namespace bustub diff --git a/test/storage/b_plus_tree_concurrent_test.cpp b/test/storage/b_plus_tree_concurrent_test.cpp index 666c124a6..9aff57211 100644 --- a/test/storage/b_plus_tree_concurrent_test.cpp +++ b/test/storage/b_plus_tree_concurrent_test.cpp @@ -129,11 +129,10 @@ TEST(BPlusTreeConcurrentTest, DISABLED_InsertTest1) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); // keys to Insert std::vector keys; int64_t scale_factor = 100; @@ -166,7 +165,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_InsertTest1) { EXPECT_EQ(current_key, keys.size() + 1); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } @@ -176,11 +174,10 @@ TEST(BPlusTreeConcurrentTest, DISABLED_InsertTest2) { GenericComparator<8> comparator(key_schema.get()); auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); // keys to Insert std::vector keys; int64_t scale_factor = 100; @@ -213,7 +210,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_InsertTest2) { EXPECT_EQ(current_key, keys.size() + 1); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } @@ -226,11 +222,10 @@ TEST(BPlusTreeConcurrentTest, DISABLED_DeleteTest1) { auto *bpm = new BufferPoolManager(50, disk_manager.get()); GenericKey<8> index_key; - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); // sequential insert std::vector keys = {1, 2, 3, 4, 5}; InsertHelper(&tree, keys); @@ -252,7 +247,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_DeleteTest1) { EXPECT_EQ(size, 1); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } @@ -264,11 +258,10 @@ TEST(BPlusTreeConcurrentTest, DISABLED_DeleteTest2) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); GenericKey<8> index_key; - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); // sequential insert std::vector keys = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; @@ -291,7 +284,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_DeleteTest2) { EXPECT_EQ(size, 4); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } @@ -303,11 +295,10 @@ TEST(BPlusTreeConcurrentTest, DISABLED_MixTest1) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); GenericKey<8> index_key; // first, populate index std::vector keys = {1, 2, 3, 4, 5}; @@ -332,7 +323,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_MixTest1) { EXPECT_EQ(size, 5); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } @@ -344,10 +334,8 @@ TEST(BPlusTreeConcurrentTest, DISABLED_MixTest2) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto *header_page = bpm->NewPage(&page_id); - (void)header_page; + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); @@ -398,7 +386,6 @@ TEST(BPlusTreeConcurrentTest, DISABLED_MixTest2) { ASSERT_EQ(size, perserved_keys.size()); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete bpm; } diff --git a/test/storage/b_plus_tree_contention_test.cpp b/test/storage/b_plus_tree_contention_test.cpp index 16f9348d9..be78f4cbb 100644 --- a/test/storage/b_plus_tree_contention_test.cpp +++ b/test/storage/b_plus_tree_contention_test.cpp @@ -27,10 +27,8 @@ bool BPlusTreeLockBenchmarkCall(size_t num_threads, int leaf_node_size, bool wit auto *disk_manager = new DiskManagerMemory(256 << 10); // 1GB auto *bpm = new BufferPoolManager(64, disk_manager); - // create and fetch header_page - page_id_t page_id; - auto *header_page = bpm->NewPage(&page_id); - (void)header_page; + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator, leaf_node_size, 10); @@ -69,7 +67,6 @@ bool BPlusTreeLockBenchmarkCall(size_t num_threads, int leaf_node_size, bool wit thread.join(); } - bpm->UnpinPage(HEADER_PAGE_ID, true); delete disk_manager; delete bpm; diff --git a/test/storage/b_plus_tree_delete_test.cpp b/test/storage/b_plus_tree_delete_test.cpp index b93a6b25f..99a4e40b7 100644 --- a/test/storage/b_plus_tree_delete_test.cpp +++ b/test/storage/b_plus_tree_delete_test.cpp @@ -30,11 +30,10 @@ TEST(BPlusTreeTests, DISABLED_DeleteTest1) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); GenericKey<8> index_key; RID rid; // create transaction @@ -85,7 +84,6 @@ TEST(BPlusTreeTests, DISABLED_DeleteTest1) { EXPECT_EQ(size, 3); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } @@ -97,11 +95,10 @@ TEST(BPlusTreeTests, DISABLED_DeleteTest2) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); GenericKey<8> index_key; RID rid; // create transaction @@ -152,7 +149,6 @@ TEST(BPlusTreeTests, DISABLED_DeleteTest2) { EXPECT_EQ(size, 1); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } diff --git a/test/storage/b_plus_tree_insert_test.cpp b/test/storage/b_plus_tree_insert_test.cpp index 91f1e9934..3267df7aa 100644 --- a/test/storage/b_plus_tree_insert_test.cpp +++ b/test/storage/b_plus_tree_insert_test.cpp @@ -30,12 +30,10 @@ TEST(BPlusTreeTests, DISABLED_InsertTest1) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); - ASSERT_EQ(page_id, HEADER_PAGE_ID); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator, 2, 3); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator, 2, 3); GenericKey<8> index_key; RID rid; // create transaction @@ -48,16 +46,15 @@ TEST(BPlusTreeTests, DISABLED_InsertTest1) { tree.Insert(index_key, rid, transaction); auto root_page_id = tree.GetRootPageId(); - auto root_page = reinterpret_cast(bpm->FetchPage(root_page_id)->GetData()); + auto root_page_guard = bpm->ReadPage(root_page_id); + auto root_page = root_page_guard.As(); ASSERT_NE(root_page, nullptr); ASSERT_TRUE(root_page->IsLeafPage()); - auto root_as_leaf = reinterpret_cast, RID, GenericComparator<8>> *>(root_page); + auto root_as_leaf = root_page_guard.As, RID, GenericComparator<8>>>(); ASSERT_EQ(root_as_leaf->GetSize(), 1); ASSERT_EQ(comparator(root_as_leaf->KeyAt(0), index_key), 0); - bpm->UnpinPage(root_page_id, false); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } @@ -69,11 +66,10 @@ TEST(BPlusTreeTests, DISABLED_InsertTest2) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator, 2, 3); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator, 2, 3); GenericKey<8> index_key; RID rid; // create transaction @@ -115,7 +111,6 @@ TEST(BPlusTreeTests, DISABLED_InsertTest2) { EXPECT_EQ(size, keys.size()); - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } @@ -127,13 +122,10 @@ TEST(BPlusTreeTests, DISABLED_InsertTest3) { auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(50, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); - ASSERT_EQ(page_id, HEADER_PAGE_ID); - + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", header_page->GetPageId(), bpm, comparator); + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator); GenericKey<8> index_key; RID rid; // create transaction @@ -180,7 +172,6 @@ TEST(BPlusTreeTests, DISABLED_InsertTest3) { current_key = current_key + 1; } - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } diff --git a/test/storage/b_plus_tree_sequential_scale_test.cpp b/test/storage/b_plus_tree_sequential_scale_test.cpp index 0c7279bef..ed093c496 100644 --- a/test/storage/b_plus_tree_sequential_scale_test.cpp +++ b/test/storage/b_plus_tree_sequential_scale_test.cpp @@ -35,10 +35,8 @@ TEST(BPlusTreeTests, DISABLED_ScaleTest) { // NOLINT auto disk_manager = std::make_unique(); auto *bpm = new BufferPoolManager(30, disk_manager.get()); - // create and fetch header_page - page_id_t page_id; - auto *header_page = bpm->NewPage(&page_id); - (void)header_page; + // allocate header_page + page_id_t page_id = bpm->NewPage(); // create b+ tree BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator, 2, 3); @@ -73,7 +71,6 @@ TEST(BPlusTreeTests, DISABLED_ScaleTest) { // NOLINT ASSERT_EQ(rids[0].GetSlotNum(), value); } - bpm->UnpinPage(HEADER_PAGE_ID, true); delete transaction; delete bpm; } diff --git a/test/storage/extendible_htable_page_test.cpp b/test/storage/extendible_htable_page_test.cpp index 7a4f2ba55..2cb3e1aee 100644 --- a/test/storage/extendible_htable_page_test.cpp +++ b/test/storage/extendible_htable_page_test.cpp @@ -33,57 +33,56 @@ TEST(ExtendibleHTableTest, DISABLED_BucketPageSampleTest) { auto disk_mgr = std::make_unique(); auto bpm = std::make_unique(5, disk_mgr.get()); - page_id_t bucket_page_id = INVALID_PAGE_ID; - { - BasicPageGuard guard = bpm->NewPageGuarded(&bucket_page_id); - auto bucket_page = guard.AsMut, RID, GenericComparator<8>>>(); - bucket_page->Init(10); - - auto key_schema = ParseCreateStatement("a bigint"); - GenericComparator<8> comparator(key_schema.get()); - GenericKey<8> index_key; - RID rid; - - // insert a few (key, value) pairs - for (int64_t i = 0; i < 10; i++) { + page_id_t bucket_page_id = bpm->NewPage(); + auto guard = bpm->WritePage(bucket_page_id); + auto bucket_page = guard.AsMut, RID, GenericComparator<8>>>(); + bucket_page->Init(10); + + auto key_schema = ParseCreateStatement("a bigint"); + GenericComparator<8> comparator(key_schema.get()); + GenericKey<8> index_key; + RID rid; + + // insert a few (key, value) pairs + for (int64_t i = 0; i < 10; i++) { + index_key.SetFromInteger(i); + rid.Set(i, i); + ASSERT_TRUE(bucket_page->Insert(index_key, rid, comparator)); + } + + index_key.SetFromInteger(11); + rid.Set(11, 11); + ASSERT_TRUE(bucket_page->IsFull()); + ASSERT_FALSE(bucket_page->Insert(index_key, rid, comparator)); + + // check for the inserted pairs + for (unsigned i = 0; i < 10; i++) { + index_key.SetFromInteger(i); + ASSERT_TRUE(bucket_page->Lookup(index_key, rid, comparator)); + ASSERT_EQ(rid, RID(i, i)); + } + + // remove a few pairs + for (unsigned i = 0; i < 10; i++) { + if (i % 2 == 1) { index_key.SetFromInteger(i); - rid.Set(i, i); - ASSERT_TRUE(bucket_page->Insert(index_key, rid, comparator)); + ASSERT_TRUE(bucket_page->Remove(index_key, comparator)); } + } - index_key.SetFromInteger(11); - rid.Set(11, 11); - ASSERT_TRUE(bucket_page->IsFull()); - ASSERT_FALSE(bucket_page->Insert(index_key, rid, comparator)); - - // check for the inserted pairs - for (unsigned i = 0; i < 10; i++) { + for (unsigned i = 0; i < 10; i++) { + if (i % 2 == 1) { + // remove the same pairs again index_key.SetFromInteger(i); - ASSERT_TRUE(bucket_page->Lookup(index_key, rid, comparator)); - ASSERT_EQ(rid, RID(i, i)); - } - - // remove a few pairs - for (unsigned i = 0; i < 10; i++) { - if (i % 2 == 1) { - index_key.SetFromInteger(i); - ASSERT_TRUE(bucket_page->Remove(index_key, comparator)); - } - } - - for (unsigned i = 0; i < 10; i++) { - if (i % 2 == 1) { - // remove the same pairs again - index_key.SetFromInteger(i); - ASSERT_FALSE(bucket_page->Remove(index_key, comparator)); - } else { - index_key.SetFromInteger(i); - ASSERT_TRUE(bucket_page->Remove(index_key, comparator)); - } + ASSERT_FALSE(bucket_page->Remove(index_key, comparator)); + } else { + index_key.SetFromInteger(i); + ASSERT_TRUE(bucket_page->Remove(index_key, comparator)); } + } - ASSERT_TRUE(bucket_page->IsEmpty()); - } // page guard dropped + ASSERT_TRUE(bucket_page->IsEmpty()); + // page guard dropped } // NOLINTNEXTLINE @@ -91,189 +90,188 @@ TEST(ExtendibleHTableTest, DISABLED_HeaderDirectoryPageSampleTest) { auto disk_mgr = std::make_unique(); auto bpm = std::make_unique(5, disk_mgr.get()); - page_id_t header_page_id = INVALID_PAGE_ID; - page_id_t directory_page_id = INVALID_PAGE_ID; - page_id_t bucket_page_id_1 = INVALID_PAGE_ID; - page_id_t bucket_page_id_2 = INVALID_PAGE_ID; - page_id_t bucket_page_id_3 = INVALID_PAGE_ID; - page_id_t bucket_page_id_4 = INVALID_PAGE_ID; - { - /************************ HEADER PAGE TEST ************************/ - BasicPageGuard header_guard = bpm->NewPageGuarded(&header_page_id); - auto header_page = header_guard.AsMut(); - header_page->Init(2); - - /* Test hashes for header page - 00000000000000001000000000000000 - 32768 - 01000000000000001000000000000000 - 1073774592 - 10000000000000001000000000000000 - 2147516416 - 11000000000000001000000000000000 - 3221258240 - */ - - // ensure we are hashing into proper bucket based on upper 2 bits - uint32_t hashes[4]{32768, 1073774592, 2147516416, 3221258240}; - for (uint32_t i = 0; i < 4; i++) { - ASSERT_EQ(header_page->HashToDirectoryIndex(hashes[i]), i); - } - - header_guard.Drop(); - - /************************ DIRECTORY PAGE TEST ************************/ - BasicPageGuard directory_guard = bpm->NewPageGuarded(&directory_page_id); - auto directory_page = directory_guard.AsMut(); - directory_page->Init(3); - - BasicPageGuard bucket_guard_1 = bpm->NewPageGuarded(&bucket_page_id_1); - auto bucket_page_1 = bucket_guard_1.AsMut, RID, GenericComparator<8>>>(); - bucket_page_1->Init(10); - - BasicPageGuard bucket_guard_2 = bpm->NewPageGuarded(&bucket_page_id_2); - auto bucket_page_2 = bucket_guard_2.AsMut, RID, GenericComparator<8>>>(); - bucket_page_2->Init(10); - - BasicPageGuard bucket_guard_3 = bpm->NewPageGuarded(&bucket_page_id_3); - auto bucket_page_3 = bucket_guard_3.AsMut, RID, GenericComparator<8>>>(); - bucket_page_3->Init(10); - - BasicPageGuard bucket_guard_4 = bpm->NewPageGuarded(&bucket_page_id_4); - auto bucket_page_4 = bucket_guard_4.AsMut, RID, GenericComparator<8>>>(); - bucket_page_4->Init(10); - - directory_page->SetBucketPageId(0, bucket_page_id_1); - - /* - ======== DIRECTORY (global_depth_: 0) ======== - | bucket_idx | page_id | local_depth | - | 0 | 2 | 0 | - ================ END DIRECTORY ================ - */ - - directory_page->VerifyIntegrity(); - ASSERT_EQ(directory_page->Size(), 1); - ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); - - // grow the directory, local depths should change! - directory_page->SetLocalDepth(0, 1); - directory_page->IncrGlobalDepth(); - directory_page->SetBucketPageId(1, bucket_page_id_2); - directory_page->SetLocalDepth(1, 1); - - /* - ======== DIRECTORY (global_depth_: 1) ======== - | bucket_idx | page_id | local_depth | - | 0 | 2 | 1 | - | 1 | 3 | 1 | - ================ END DIRECTORY ================ - */ - - directory_page->VerifyIntegrity(); - ASSERT_EQ(directory_page->Size(), 2); - ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); - ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); - - for (uint32_t i = 0; i < 100; i++) { - ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 2); - } - - directory_page->SetLocalDepth(0, 2); - directory_page->IncrGlobalDepth(); - directory_page->SetBucketPageId(2, bucket_page_id_3); - - /* - ======== DIRECTORY (global_depth_: 2) ======== - | bucket_idx | page_id | local_depth | - | 0 | 2 | 2 | - | 1 | 3 | 1 | - | 2 | 4 | 2 | - | 3 | 3 | 1 | - ================ END DIRECTORY ================ - */ - - directory_page->VerifyIntegrity(); - ASSERT_EQ(directory_page->Size(), 4); - ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); - ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); - ASSERT_EQ(directory_page->GetBucketPageId(2), bucket_page_id_3); - ASSERT_EQ(directory_page->GetBucketPageId(3), bucket_page_id_2); - - for (uint32_t i = 0; i < 100; i++) { - ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 4); - } - - directory_page->SetLocalDepth(0, 3); - directory_page->IncrGlobalDepth(); - directory_page->SetBucketPageId(4, bucket_page_id_4); - - /* - ======== DIRECTORY (global_depth_: 3) ======== - | bucket_idx | page_id | local_depth | - | 0 | 2 | 3 | - | 1 | 3 | 1 | - | 2 | 4 | 2 | - | 3 | 3 | 1 | - | 4 | 5 | 3 | - | 5 | 3 | 1 | - | 6 | 4 | 2 | - | 7 | 3 | 1 | - ================ END DIRECTORY ================ - */ - directory_page->VerifyIntegrity(); - ASSERT_EQ(directory_page->Size(), 8); - ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); - ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); - ASSERT_EQ(directory_page->GetBucketPageId(2), bucket_page_id_3); - ASSERT_EQ(directory_page->GetBucketPageId(3), bucket_page_id_2); - ASSERT_EQ(directory_page->GetBucketPageId(4), bucket_page_id_4); - ASSERT_EQ(directory_page->GetBucketPageId(5), bucket_page_id_2); - ASSERT_EQ(directory_page->GetBucketPageId(6), bucket_page_id_3); - ASSERT_EQ(directory_page->GetBucketPageId(7), bucket_page_id_2); - - for (uint32_t i = 0; i < 100; i++) { - ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 8); - } - - // uncommenting this code line below should cause an "Assertion failed" - // since this would be exceeding the max depth we initialized - // directory_page->IncrGlobalDepth(); - - // at this time, we cannot shrink the directory since we have ld = gd = 3 - ASSERT_EQ(directory_page->CanShrink(), false); - - directory_page->SetLocalDepth(0, 2); - directory_page->SetLocalDepth(4, 2); - directory_page->SetBucketPageId(0, bucket_page_id_4); - - /* - ======== DIRECTORY (global_depth_: 3) ======== - | bucket_idx | page_id | local_depth | - | 0 | 5 | 2 | - | 1 | 3 | 1 | - | 2 | 4 | 2 | - | 3 | 3 | 1 | - | 4 | 5 | 2 | - | 5 | 3 | 1 | - | 6 | 4 | 2 | - | 7 | 3 | 1 | - ================ END DIRECTORY ================ - */ - - ASSERT_EQ(directory_page->CanShrink(), true); - directory_page->DecrGlobalDepth(); - - /* - ======== DIRECTORY (global_depth_: 2) ======== - | bucket_idx | page_id | local_depth | - | 0 | 5 | 2 | - | 1 | 3 | 1 | - | 2 | 4 | 2 | - | 3 | 3 | 1 | - ================ END DIRECTORY ================ - */ - - directory_page->VerifyIntegrity(); - ASSERT_EQ(directory_page->Size(), 4); - ASSERT_EQ(directory_page->CanShrink(), false); - } // page guard dropped + /************************ HEADER PAGE TEST ************************/ + page_id_t header_page_id = bpm->NewPage(); + auto header_guard = bpm->WritePage(header_page_id); + auto header_page = header_guard.AsMut(); + header_page->Init(2); + + /* Test hashes for header page + 00000000000000001000000000000000 - 32768 + 01000000000000001000000000000000 - 1073774592 + 10000000000000001000000000000000 - 2147516416 + 11000000000000001000000000000000 - 3221258240 + */ + + // ensure we are hashing into proper bucket based on upper 2 bits + uint32_t hashes[4]{32768, 1073774592, 2147516416, 3221258240}; + for (uint32_t i = 0; i < 4; i++) { + ASSERT_EQ(header_page->HashToDirectoryIndex(hashes[i]), i); + } + + header_guard.Drop(); + + /************************ DIRECTORY PAGE TEST ************************/ + page_id_t directory_page_id = bpm->NewPage(); + auto directory_guard = bpm->WritePage(directory_page_id); + auto directory_page = directory_guard.AsMut(); + directory_page->Init(3); + + page_id_t bucket_page_id_1 = bpm->NewPage(); + auto bucket_guard_1 = bpm->WritePage(bucket_page_id_1); + auto bucket_page_1 = bucket_guard_1.AsMut, RID, GenericComparator<8>>>(); + bucket_page_1->Init(10); + + page_id_t bucket_page_id_2 = bpm->NewPage(); + auto bucket_guard_2 = bpm->WritePage(bucket_page_id_2); + auto bucket_page_2 = bucket_guard_2.AsMut, RID, GenericComparator<8>>>(); + bucket_page_2->Init(10); + + page_id_t bucket_page_id_3 = bpm->NewPage(); + auto bucket_guard_3 = bpm->WritePage(bucket_page_id_3); + auto bucket_page_3 = bucket_guard_3.AsMut, RID, GenericComparator<8>>>(); + bucket_page_3->Init(10); + + page_id_t bucket_page_id_4 = bpm->NewPage(); + auto bucket_guard_4 = bpm->WritePage(bucket_page_id_4); + auto bucket_page_4 = bucket_guard_4.AsMut, RID, GenericComparator<8>>>(); + bucket_page_4->Init(10); + + directory_page->SetBucketPageId(0, bucket_page_id_1); + + /* + ======== DIRECTORY (global_depth_: 0) ======== + | bucket_idx | page_id | local_depth | + | 0 | 2 | 0 | + ================ END DIRECTORY ================ + */ + + directory_page->VerifyIntegrity(); + ASSERT_EQ(directory_page->Size(), 1); + ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); + + // grow the directory, local depths should change! + directory_page->SetLocalDepth(0, 1); + directory_page->IncrGlobalDepth(); + directory_page->SetBucketPageId(1, bucket_page_id_2); + directory_page->SetLocalDepth(1, 1); + + /* + ======== DIRECTORY (global_depth_: 1) ======== + | bucket_idx | page_id | local_depth | + | 0 | 2 | 1 | + | 1 | 3 | 1 | + ================ END DIRECTORY ================ + */ + + directory_page->VerifyIntegrity(); + ASSERT_EQ(directory_page->Size(), 2); + ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); + ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); + + for (uint32_t i = 0; i < 100; i++) { + ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 2); + } + + directory_page->SetLocalDepth(0, 2); + directory_page->IncrGlobalDepth(); + directory_page->SetBucketPageId(2, bucket_page_id_3); + + /* + ======== DIRECTORY (global_depth_: 2) ======== + | bucket_idx | page_id | local_depth | + | 0 | 2 | 2 | + | 1 | 3 | 1 | + | 2 | 4 | 2 | + | 3 | 3 | 1 | + ================ END DIRECTORY ================ + */ + + directory_page->VerifyIntegrity(); + ASSERT_EQ(directory_page->Size(), 4); + ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); + ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); + ASSERT_EQ(directory_page->GetBucketPageId(2), bucket_page_id_3); + ASSERT_EQ(directory_page->GetBucketPageId(3), bucket_page_id_2); + + for (uint32_t i = 0; i < 100; i++) { + ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 4); + } + + directory_page->SetLocalDepth(0, 3); + directory_page->IncrGlobalDepth(); + directory_page->SetBucketPageId(4, bucket_page_id_4); + + /* + ======== DIRECTORY (global_depth_: 3) ======== + | bucket_idx | page_id | local_depth | + | 0 | 2 | 3 | + | 1 | 3 | 1 | + | 2 | 4 | 2 | + | 3 | 3 | 1 | + | 4 | 5 | 3 | + | 5 | 3 | 1 | + | 6 | 4 | 2 | + | 7 | 3 | 1 | + ================ END DIRECTORY ================ + */ + directory_page->VerifyIntegrity(); + ASSERT_EQ(directory_page->Size(), 8); + ASSERT_EQ(directory_page->GetBucketPageId(0), bucket_page_id_1); + ASSERT_EQ(directory_page->GetBucketPageId(1), bucket_page_id_2); + ASSERT_EQ(directory_page->GetBucketPageId(2), bucket_page_id_3); + ASSERT_EQ(directory_page->GetBucketPageId(3), bucket_page_id_2); + ASSERT_EQ(directory_page->GetBucketPageId(4), bucket_page_id_4); + ASSERT_EQ(directory_page->GetBucketPageId(5), bucket_page_id_2); + ASSERT_EQ(directory_page->GetBucketPageId(6), bucket_page_id_3); + ASSERT_EQ(directory_page->GetBucketPageId(7), bucket_page_id_2); + + for (uint32_t i = 0; i < 100; i++) { + ASSERT_EQ(directory_page->HashToBucketIndex(i), i % 8); + } + + // uncommenting this code line below should cause an "Assertion failed" + // since this would be exceeding the max depth we initialized + // directory_page->IncrGlobalDepth(); + + // at this time, we cannot shrink the directory since we have ld = gd = 3 + ASSERT_EQ(directory_page->CanShrink(), false); + + directory_page->SetLocalDepth(0, 2); + directory_page->SetLocalDepth(4, 2); + directory_page->SetBucketPageId(0, bucket_page_id_4); + + /* + ======== DIRECTORY (global_depth_: 3) ======== + | bucket_idx | page_id | local_depth | + | 0 | 5 | 2 | + | 1 | 3 | 1 | + | 2 | 4 | 2 | + | 3 | 3 | 1 | + | 4 | 5 | 2 | + | 5 | 3 | 1 | + | 6 | 4 | 2 | + | 7 | 3 | 1 | + ================ END DIRECTORY ================ + */ + + ASSERT_EQ(directory_page->CanShrink(), true); + directory_page->DecrGlobalDepth(); + + /* + ======== DIRECTORY (global_depth_: 2) ======== + | bucket_idx | page_id | local_depth | + | 0 | 5 | 2 | + | 1 | 3 | 1 | + | 2 | 4 | 2 | + | 3 | 3 | 1 | + ================ END DIRECTORY ================ + */ + + directory_page->VerifyIntegrity(); + ASSERT_EQ(directory_page->Size(), 4); + ASSERT_EQ(directory_page->CanShrink(), false); + // page guard dropped } } // namespace bustub diff --git a/test/storage/page_guard_test.cpp b/test/storage/page_guard_test.cpp index efa109bf3..c3621e17b 100644 --- a/test/storage/page_guard_test.cpp +++ b/test/storage/page_guard_test.cpp @@ -22,32 +22,175 @@ namespace bustub { -// NOLINTNEXTLINE -TEST(PageGuardTest, DISABLED_SampleTest) { - const size_t buffer_pool_size = 5; - const size_t k = 2; +const size_t FRAMES = 10; +const size_t K_DIST = 2; +TEST(PageGuardTest, DISABLED_DropTest) { auto disk_manager = std::make_shared(); - auto bpm = std::make_shared(buffer_pool_size, disk_manager.get(), k); + auto bpm = std::make_shared(FRAMES, disk_manager.get(), K_DIST); - page_id_t page_id_temp; - auto *page0 = bpm->NewPage(&page_id_temp); + { + auto pid0 = bpm->NewPage(); + auto page0 = bpm->WritePage(pid0); + + // The page should be pinned. + ASSERT_EQ(1, bpm->GetPinCount(pid0)); + + // A drop should unpin the page. + page0.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pid0)); + + // Another drop should have no effect. + page0.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pid0)); + } // Destructor should be called. Useless but should not cause issues. + + auto pid1 = bpm->NewPage(); + auto pid2 = bpm->NewPage(); + + { + auto read_guarded_page = bpm->ReadPage(pid1); + auto write_guarded_page = bpm->WritePage(pid2); + + ASSERT_EQ(1, bpm->GetPinCount(pid1)); + ASSERT_EQ(1, bpm->GetPinCount(pid2)); + + // Dropping should unpin the pages. + read_guarded_page.Drop(); + write_guarded_page.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pid1)); + ASSERT_EQ(0, bpm->GetPinCount(pid2)); - auto guarded_page = BasicPageGuard(bpm.get(), page0); + // Another drop should have no effect. + read_guarded_page.Drop(); + write_guarded_page.Drop(); + ASSERT_EQ(0, bpm->GetPinCount(pid1)); + ASSERT_EQ(0, bpm->GetPinCount(pid2)); + } // Destructor should be called. Useless but should not cause issues. - EXPECT_EQ(page0->GetData(), guarded_page.GetData()); - EXPECT_EQ(page0->GetPageId(), guarded_page.PageId()); - EXPECT_EQ(1, page0->GetPinCount()); + // This will hang if the latches were not unlocked correctly in the destructors. + { + auto write_test1 = bpm->WritePage(pid1); + auto write_test2 = bpm->WritePage(pid2); + } - guarded_page.Drop(); + std::vector page_ids; + { + // Fill up the BPM. + std::vector guards; + for (size_t i = 0; i < FRAMES; i++) { + auto new_pid = bpm->NewPage(); + guards.push_back(bpm->WritePage(new_pid)); + ASSERT_EQ(1, bpm->GetPinCount(new_pid)); + page_ids.push_back(new_pid); + } + } // This drops all of the guards. + + for (size_t i = 0; i < FRAMES; i++) { + ASSERT_EQ(0, bpm->GetPinCount(page_ids[i])); + } - EXPECT_EQ(0, page0->GetPinCount()); + // Get a new write page and edit it. We will retrieve it later + auto mutable_page_id = bpm->NewPage(); + auto mutable_guard = bpm->WritePage(mutable_page_id); + strcpy(mutable_guard.GetDataMut(), "data"); // NOLINT + mutable_guard.Drop(); { - auto *page2 = bpm->NewPage(&page_id_temp); - auto guard2 = ReadPageGuard(bpm.get(), page2); + // Fill up the BPM again. + std::vector guards; + for (size_t i = 0; i < FRAMES; i++) { + auto new_pid = bpm->NewPage(); + guards.push_back(bpm->WritePage(new_pid)); + ASSERT_EQ(1, bpm->GetPinCount(new_pid)); + } } + // Fetching the flushed page should result in seeing the changed value. + auto immutable_guard = bpm->ReadPage(mutable_page_id); + ASSERT_EQ(0, std::strcmp("data", immutable_guard.GetData())); + + // Shutdown the disk manager and remove the temporary file we created. + disk_manager->ShutDown(); +} + +TEST(PageGuardTest, DISABLED_MoveTest) { + auto disk_manager = std::make_shared(); + auto bpm = std::make_shared(FRAMES, disk_manager.get(), K_DIST); + + auto pid0 = bpm->NewPage(); + auto pid1 = bpm->NewPage(); + auto pid2 = bpm->NewPage(); + auto pid3 = bpm->NewPage(); + auto pid4 = bpm->NewPage(); + auto pid5 = bpm->NewPage(); + + auto guard0 = bpm->ReadPage(pid0); + auto guard1 = bpm->ReadPage(pid1); + ASSERT_EQ(1, bpm->GetPinCount(pid0)); + ASSERT_EQ(1, bpm->GetPinCount(pid1)); + + // This shouldn't change pin counts... + auto &guard0_r = guard0; + guard0 = std::move(guard0_r); + ASSERT_EQ(1, bpm->GetPinCount(pid0)); + + // Invalidate the old guard0 by move assignment. + guard0 = std::move(guard1); + ASSERT_EQ(0, bpm->GetPinCount(pid0)); + ASSERT_EQ(1, bpm->GetPinCount(pid1)); + + // Invalidate the old guard0 by move construction. + auto guard0a(std::move(guard0)); + ASSERT_EQ(0, bpm->GetPinCount(pid0)); + ASSERT_EQ(1, bpm->GetPinCount(pid1)); + + auto guard2 = bpm->ReadPage(pid2); + auto guard3 = bpm->ReadPage(pid3); + ASSERT_EQ(1, bpm->GetPinCount(pid2)); + ASSERT_EQ(1, bpm->GetPinCount(pid3)); + + // This shouldn't change pin counts... + auto &guard2_r = guard2; + guard2 = std::move(guard2_r); + ASSERT_EQ(1, bpm->GetPinCount(pid2)); + + // Invalidate the old guard3 by move assignment. + guard2 = std::move(guard3); + ASSERT_EQ(0, bpm->GetPinCount(pid2)); + ASSERT_EQ(1, bpm->GetPinCount(pid3)); + + // Invalidate the old guard2 by move construction. + auto guard2a(std::move(guard2)); + ASSERT_EQ(0, bpm->GetPinCount(pid2)); + ASSERT_EQ(1, bpm->GetPinCount(pid3)); + + // This will hang if page 2 was not unlatched correctly. + { auto temp_guard2 = bpm->WritePage(pid2); } + + auto guard4 = bpm->WritePage(pid4); + auto guard5 = bpm->WritePage(pid5); + ASSERT_EQ(1, bpm->GetPinCount(pid4)); + ASSERT_EQ(1, bpm->GetPinCount(pid5)); + + // This shouldn't change pin counts... + auto &guard4_r = guard4; + guard4 = std::move(guard4_r); + ASSERT_EQ(1, bpm->GetPinCount(pid4)); + + // Invalidate the old guard5 by move assignment. + guard4 = std::move(guard5); + ASSERT_EQ(0, bpm->GetPinCount(pid4)); + ASSERT_EQ(1, bpm->GetPinCount(pid5)); + + // Invalidate the old guard4 by move construction. + auto guard4a(std::move(guard4)); + ASSERT_EQ(0, bpm->GetPinCount(pid4)); + ASSERT_EQ(1, bpm->GetPinCount(pid5)); + + // This will hang if page 4 was not unlatched correctly. + { auto temp_guard4 = bpm->ReadPage(pid4); } + // Shutdown the disk manager and remove the temporary file we created. disk_manager->ShutDown(); } diff --git a/test/storage/write_back_cache_test.cpp b/test/storage/write_back_cache_test.cpp deleted file mode 100644 index c2463854c..000000000 --- a/test/storage/write_back_cache_test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// BusTub -// -// write_back_cache_test.cpp -// -// Identification: test/storage/write_back_cache_test.cpp -// -// Copyright (c) 2015-2024, Carnegie Mellon University Database Group -// -//===----------------------------------------------------------------------===// - -#include "storage/disk/write_back_cache.h" -#include "gtest/gtest.h" - -namespace bustub { - -// NOLINTNEXTLINE -TEST(WriteBackCacheTest, ScheduleWriteReadPageTest) { - WriteBackCache wbc{}; - std::vector wbc_pages{}; - for (size_t i{0}; i < 8; i++) { - Page bpm_page{}; - auto content{"Meuh!: " + std::to_string(i) + " 🐄🐄🐄🐄"}; - std::strncpy(bpm_page.GetData(), content.data(), BUSTUB_PAGE_SIZE); - - Page *wbc_page{wbc.Add(&bpm_page)}; - EXPECT_NE(wbc_page, nullptr); - EXPECT_NE(wbc_page, &bpm_page); - wbc_pages.push_back(wbc_page); - EXPECT_EQ(std::memcmp(wbc_page->GetData(), bpm_page.GetData(), BUSTUB_PAGE_SIZE), 0); - } - - Page extra_page{}; - Page *wbc_extra_page{wbc.Add(&extra_page)}; - EXPECT_EQ(wbc_extra_page, nullptr); - - for (size_t i{0}; i < 8; i++) { - auto check{"Meuh!: " + std::to_string(i) + " 🐄🐄🐄🐄"}; - EXPECT_EQ(std::memcmp(wbc_pages[i]->GetData(), check.data(), check.size()), 0); - } - - wbc.Remove(wbc_pages[5]); - Page replace_page{}; - wbc_pages[5] = wbc.Add(&replace_page); - EXPECT_NE(wbc_pages[5], nullptr); - - for (size_t i{0}; i < 8; i++) { - wbc.Remove(wbc_pages[i]); // Shouldn't crash. - } - - wbc.Remove(nullptr); // Shouldn't crash. -} - -} // namespace bustub diff --git a/tools/b_plus_tree_printer/b_plus_tree_printer.cpp b/tools/b_plus_tree_printer/b_plus_tree_printer.cpp index c8c628a24..5e8c041fe 100644 --- a/tools/b_plus_tree_printer/b_plus_tree_printer.cpp +++ b/tools/b_plus_tree_printer/b_plus_tree_printer.cpp @@ -74,11 +74,11 @@ auto main(int argc, char **argv) -> int { auto *disk_manager = new DiskManager("test.bustub"); auto *bpm = new BufferPoolManager(100, disk_manager); + // create and fetch header_page - page_id_t page_id; - auto header_page = bpm->NewPage(&page_id); + page_id_t root_pid = bpm->NewPage(); // create b+ tree - BPlusTree, RID, GenericComparator<8>> tree("foo_pk", page_id, bpm, comparator, leaf_max_size, + BPlusTree, RID, GenericComparator<8>> tree("foo_pk", root_pid, bpm, comparator, leaf_max_size, internal_max_size); // create transaction auto *transaction = new Transaction(0); @@ -132,7 +132,9 @@ auto main(int argc, char **argv) -> int { break; } } - bpm->UnpinPage(header_page->GetPageId(), true); + + BUSTUB_ASSERT(bpm->DeletePage(root_pid), "Unable to delete root page for some reason"); + delete bpm; delete transaction; delete disk_manager; diff --git a/tools/bpm_bench/bpm_bench.cpp b/tools/bpm_bench/bpm_bench.cpp index 683f5b8dc..31cb0308e 100644 --- a/tools/bpm_bench/bpm_bench.cpp +++ b/tools/bpm_bench/bpm_bench.cpp @@ -204,15 +204,11 @@ auto main(int argc, char **argv) -> int { bustub_page_cnt, duration_ms, enable_latency, lru_k_size, bustub_bpm_size, scan_thread_n, get_thread_n); for (size_t i = 0; i < bustub_page_cnt; i++) { - page_id_t page_id; - auto *page = bpm->NewPage(&page_id); - if (page == nullptr) { - throw std::runtime_error("new page failed"); + page_id_t page_id = bpm->NewPage(); + { + auto guard = bpm->WritePage(page_id); + ModifyPage(guard.GetDataMut(), i, 0); } - - ModifyPage(page->GetData(), i, 0); - - bpm->UnpinPage(page_id, true); page_ids.push_back(page_id); } @@ -229,8 +225,6 @@ auto main(int argc, char **argv) -> int { for (size_t thread_id = 0; thread_id < scan_thread_n; thread_id++) { threads.emplace_back([bustub_page_cnt, scan_thread_n, thread_id, &page_ids, &bpm, duration_ms, &total_metrics] { - ModifyRecord records; - BpmMetrics metrics(fmt::format("scan {:>2}", thread_id), duration_ms); metrics.Begin(); @@ -239,19 +233,11 @@ auto main(int argc, char **argv) -> int { size_t page_idx = page_idx_start; while (!metrics.ShouldFinish()) { - auto *page = bpm->FetchPage(page_ids[page_idx], AccessType::Scan); - if (page == nullptr) { - continue; + { + auto page = bpm->ReadPage(page_ids[page_idx], AccessType::Scan); + CheckPageConsistentNoSeed(page.GetData(), page_idx); } - page->WLatch(); - auto &seed = records[page_idx]; - CheckPageConsistent(page->GetData(), page_idx, seed); - seed = seed + 1; - ModifyPage(page->GetData(), page_idx, seed); - page->WUnlatch(); - - bpm->UnpinPage(page->GetPageId(), true, AccessType::Scan); page_idx += 1; if (page_idx >= page_idx_end) { page_idx = page_idx_start; @@ -265,27 +251,26 @@ auto main(int argc, char **argv) -> int { } for (size_t thread_id = 0; thread_id < get_thread_n; thread_id++) { - threads.emplace_back([thread_id, &page_ids, &bpm, bustub_page_cnt, duration_ms, &total_metrics] { + threads.emplace_back([thread_id, &page_ids, &bpm, bustub_page_cnt, get_thread_n, duration_ms, &total_metrics] { std::random_device r; std::default_random_engine gen(r()); zipfian_int_distribution dist(0, bustub_page_cnt - 1, 0.8); + ModifyRecord records; - BpmMetrics metrics(fmt::format("get {:>2}", thread_id), duration_ms); + BpmMetrics metrics(fmt::format("get {:>2}", thread_id), duration_ms); metrics.Begin(); while (!metrics.ShouldFinish()) { - auto page_idx = dist(gen); - auto *page = bpm->FetchPage(page_ids[page_idx], AccessType::Lookup); - if (page == nullptr) { - fmt::println(stderr, "cannot fetch page"); - std::terminate(); + auto rand = dist(gen); + auto page_idx = std::min(rand / get_thread_n * get_thread_n + thread_id, bustub_page_cnt - 1); + { + auto page = bpm->WritePage(page_ids[page_idx], AccessType::Lookup); + auto &seed = records[page_idx]; + CheckPageConsistent(page.GetData(), page_idx, seed); + seed = seed + 1; + ModifyPage(page.GetDataMut(), page_idx, seed); } - page->RLatch(); - CheckPageConsistentNoSeed(page->GetData(), page_idx); - page->RUnlatch(); - - bpm->UnpinPage(page->GetPageId(), false, AccessType::Lookup); metrics.Tick(); metrics.Report(); } diff --git a/tools/btree_bench/btree_bench.cpp b/tools/btree_bench/btree_bench.cpp index 76452b563..1335343e8 100644 --- a/tools/btree_bench/btree_bench.cpp +++ b/tools/btree_bench/btree_bench.cpp @@ -142,8 +142,7 @@ auto main(int argc, char **argv) -> int { auto key_schema = bustub::ParseCreateStatement("a bigint"); bustub::GenericComparator<8> comparator(key_schema.get()); - page_id_t page_id; - auto header_page = bpm->NewPageGuarded(&page_id); + page_id_t page_id = bpm->NewPage(); bustub::BPlusTree, bustub::RID, bustub::GenericComparator<8>> index("foo_pk", page_id, bpm.get(), comparator);