diff --git a/.gitignore b/.gitignore index 1d7d4ea..5992cad 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ a8/ CMakeUserPresets.json src/CMakeUserPresets.json __pycache__/ +compile_commands.json +.cache/ diff --git a/conan.lock b/conan.lock index 6dec147..40d9c06 100644 --- a/conan.lock +++ b/conan.lock @@ -1,7 +1,7 @@ { "version": "0.5", "requires": [ - "batteries/0.62.0", + "batteries/0.63.0", "boost/1.88.0", "bzip2/1.0.8", "cli11/2.5.0", diff --git a/conanfile.py b/conanfile.py index 4e38d61..a6a4818 100644 --- a/conanfile.py +++ b/conanfile.py @@ -74,7 +74,7 @@ def requirements(self): VISIBLE = self.cor.VISIBLE OVERRIDE = self.cor.OVERRIDE - self.requires("batteries/[>=0.62.0 <1]", **VISIBLE) + self.requires("batteries/[>=0.63.0 <1]", **VISIBLE) self.requires("boost/[>=1.88.0 <2]", **VISIBLE) self.requires("cli11/[>=2.5.0 <3]", **VISIBLE) self.requires("glog/[>=0.7.1 <1]", **VISIBLE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb0476a..e678903 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,8 +10,11 @@ cmake_minimum_required(VERSION 3.20) macro(LLFS_CollectHeaders TARGET_NAME TARGET_SRCDIR) - file(GLOB_RECURSE ${TARGET_NAME}_Headers RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ./${TARGET_SRCDIR}/*.hpp ./${TARGET_SRCDIR}/*.ipp) - + file(GLOB_RECURSE ${TARGET_NAME}_Headers + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ./${TARGET_SRCDIR}/*.hpp + ./${TARGET_SRCDIR}/*.ipp) + foreach(_header IN LISTS ${TARGET_NAME}_Headers) get_filename_component(_destination "${_header}" PATH) install(FILES "${_header}" DESTINATION include/${_destination}) @@ -23,7 +26,7 @@ endmacro() macro(LLFS_DefineLibrary TARGET_NAME TARGET_SRCDIR) set(TARGET_DEPS ${ARGN}) - + file(GLOB ${TARGET_NAME}_LibSources ./${TARGET_SRCDIR}/*.cpp ./${TARGET_SRCDIR}/*/*.cpp @@ -41,7 +44,7 @@ macro(LLFS_DefineLibrary TARGET_NAME TARGET_SRCDIR) ./${TARGET_SRCDIR}/*/*/*.test.cpp ./${TARGET_SRCDIR}/*/*/*/*.test.cpp ) - + foreach (_file "FORCE_LIST_NOT_EMPTY;${${TARGET_NAME}_TestSources}") list(REMOVE_ITEM ${TARGET_NAME}_LibSources ${_file}) endforeach () @@ -51,18 +54,11 @@ macro(LLFS_DefineLibrary TARGET_NAME TARGET_SRCDIR) add_library(${TARGET_NAME} ${${TARGET_NAME}_LibSources}) target_link_libraries(${TARGET_NAME} ${TARGET_DEPS}) - # Define unit test target - # - add_executable(${TARGET_NAME}_Test ${${TARGET_NAME}_TestSources} ./common/test_environment.cpp) - target_link_libraries( - ${TARGET_NAME}_Test - ${TARGET_NAME} - ${TARGET_DEPS} + set(EXTRA_TEST_DEPS GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main - libfuse::libfuse OpenSSL::Crypto Boost::context Boost::stacktrace_backtrace @@ -71,6 +67,20 @@ macro(LLFS_DefineLibrary TARGET_NAME TARGET_SRCDIR) dl stdc++fs) + if (LINUX) + set(EXTRA_TEST_DEPS ${EXTRA_TEST_DEPS} libfuse::libfuse) + endif() + + # Define unit test target + # + add_executable(${TARGET_NAME}_Test ${${TARGET_NAME}_TestSources} ./common/test_environment.cpp) + target_link_libraries( + ${TARGET_NAME}_Test + ${TARGET_NAME} + ${TARGET_DEPS} + ${EXTRA_TEST_DEPS} + ) + add_test(NAME ${TARGET_NAME}_Test WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin COMMAND ${TARGET_NAME}_Test) @@ -100,12 +110,18 @@ endmacro() # Library definitions. # -LLFS_DefineLibrary(llfs ./llfs +set(LLFS_Deps batteries::batteries - liburing::liburing OpenSSL::Crypto xxHash::xxhash - ) +) + +if (LINUX) + set(LLFS_Deps ${LLFS_Deps} liburing::liburing) +endif() + + +LLFS_DefineLibrary(llfs ./llfs ${LLFS_Deps}) #=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- @@ -149,9 +165,8 @@ file(GLOB llfs_fuse_Sources add_executable(llfs_fuse ${llfs_fuse_Sources}) -target_link_libraries(llfs_fuse +set(LLFS_TestDeps llfs - libfuse::libfuse batteries::batteries Boost::context Boost::stacktrace_backtrace @@ -159,4 +174,11 @@ target_link_libraries(llfs_fuse OpenSSL::Crypto CLI11::CLI11 dl - stdc++fs) \ No newline at end of file + stdc++fs +) + +if (LINUX) + set(LLFS_TestDeps ${LLFS_TestDeps} libfuse::libfuse) +endif() + +target_link_libraries(llfs_fuse ${LLFS_TestDeps}) diff --git a/src/llfs/page_allocate_options.hpp b/src/llfs/page_allocate_options.hpp new file mode 100644 index 0000000..c9d7474 --- /dev/null +++ b/src/llfs/page_allocate_options.hpp @@ -0,0 +1,132 @@ +//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ +// +// Part of the LLFS Project, under Apache License v2.0. +// See https://www.apache.org/licenses/LICENSE-2.0 for license information. +// SPDX short identifier: Apache-2.0 +// +//+++++++++++-+-+--+----- --- -- - - - - + +#pragma once +#define LLFS_PAGE_ALLOCATE_OPTIONS_HPP + +#include +// +#include +#include +#include + +#include +#include + +#include + +#include + +namespace llfs { + +struct PageAllocateOptions : PageCacheInsertOptions { + using Self = PageAllocateOptions; + using Super = PageCacheInsertOptions; + + //+++++++++++-+-+--+----- --- -- - - - - + + static const batt::CancelToken& no_cancel_token() + { + static const batt::CancelToken token_{None}; + return token_; + } + + //+++++++++++-+-+--+----- --- -- - - - - + + Optional page_layout_id_ = None; + + batt::WaitForResource wait_for_resource_ = batt::WaitForResource::kFalse; + + const batt::CancelToken* cancel_token_ = std::addressof(Self::no_cancel_token()); + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Constructs a PageAllocateOptions with default values. + */ + PageAllocateOptions() = default; + + /** \brief Constructs a copy of the given options. + */ + PageAllocateOptions(const PageAllocateOptions&) = default; + + /** \brief Copies the passed options to *this, overwriting all set values. + */ + PageAllocateOptions& operator=(const PageAllocateOptions&) = default; + + /** \brief Constructs a PageAllocateOptions object from typed arguments; these may be in any + * order. + */ + template > + explicit PageAllocateOptions(Args&&... args) noexcept + : Super{BATT_FORWARD(args)...} + , page_layout_id_{batt::get_typed_arg(Optional{None}, BATT_FORWARD(args)...)} + , wait_for_resource_{batt::get_typed_arg(batt::WaitForResource::kFalse, + BATT_FORWARD(args)...)} + , cancel_token_{std::addressof(batt::get_typed_arg( + Self::no_cancel_token(), BATT_FORWARD(args)...))} + { + static_assert(decltype(batt::has_typed_arg(BATT_FORWARD(args)...)){}); + static_assert(decltype(batt::has_typed_arg(BATT_FORWARD(args)...)){}); + } + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Returns a copy of this. + */ + Self clone() const + { + return *this; + } + + //----- --- -- - - - - + + Self& page_layout_id(const Optional& opt_layout) + { + this->page_layout_id_ = opt_layout; + return *this; + } + + const PageLayoutId& page_layout_id() const + { + BATT_CHECK(this->page_layout_id_); + return *this->page_layout_id_; + } + + //----- --- -- - - - - + + Self& wait_for_resource(batt::WaitForResource val) + { + this->wait_for_resource_ = val; + return *this; + } + + batt::WaitForResource wait_for_resource() const + { + return this->wait_for_resource_; + } + + //----- --- -- - - - - + + Self& cancel_token(const batt::CancelToken& val) + { + this->cancel_token_ = std::addressof(val); + return *this; + } + + const batt::CancelToken& cancel_token() const + { + return *this->cancel_token_; + } +}; + +//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - + +BATT_OBJECT_PRINT_IMPL((inline), PageAllocateOptions, + (wait_for_resource(), page_size(), page_layout_id(), lru_priority())) + +} // namespace llfs diff --git a/src/llfs/page_cache.cpp b/src/llfs/page_cache.cpp index 25f4edf..9afc926 100644 --- a/src/llfs/page_cache.cpp +++ b/src/llfs/page_cache.cpp @@ -412,33 +412,16 @@ std::unique_ptr PageCache::new_job() //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -StatusOr PageCache::allocate_page_of_size(PageSize size, - batt::WaitForResource wait_for_resource, - LruPriority lru_priority, u64 callers, - u64 job_id, - const batt::CancelToken& cancel_token) +StatusOr PageCache::allocate_page(const PageAllocateOptions& options, + u64 callers [[maybe_unused]], + u64 job_id [[maybe_unused]]) { - const PageSizeLog2 size_log2 = log2_ceil(size); - BATT_CHECK_EQ(usize{1} << size_log2, size) << "size must be a power of 2"; + BATT_CHECK_EQ(batt::bit_count(options.page_size()), 1); - return this->allocate_page_of_size_log2(size_log2, wait_for_resource, lru_priority, - callers | Caller::PageCache_allocate_page_of_size, job_id, - std::move(cancel_token)); -} + PageSizeLog2 size_log2{BATT_CHECKED_CAST(u32, batt::log2_ceil(options.page_size()))}; -//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - -// -StatusOr PageCache::allocate_page_of_size_log2(PageSizeLog2 size_log2, - batt::WaitForResource wait_for_resource, - LruPriority lru_priority, - u64 callers [[maybe_unused]], - u64 job_id [[maybe_unused]], - const batt::CancelToken& cancel_token) -{ BATT_CHECK_LT(size_log2, kMaxPageSizeLog2); - PageSize page_size{u32{1} << size_log2}; - LatencyTimer alloc_timer{this->metrics_.allocate_page_alloc_latency}; Slice device_entries = this->devices_with_page_size_log2(size_log2); @@ -452,7 +435,7 @@ StatusOr PageCache::allocate_page_of_size_log2(PageSizeLog2 size_log continue; } PageArena& arena = device_entry->arena; - StatusOr page_id = arena.allocator().allocate_page(wait_arg, cancel_token); + StatusOr page_id = arena.allocator().allocate_page(wait_arg, options.cancel_token()); if (!page_id.ok()) { if (page_id.status() == batt::StatusCode::kResourceExhausted) { const u64 page_size = u64{1} << size_log2; @@ -476,16 +459,15 @@ StatusOr PageCache::allocate_page_of_size_log2(PageSizeLog2 size_log }); #endif // LLFS_TRACK_NEW_PAGE_EVENTS - return this->pin_allocated_page_to_cache(device_entry, page_size, *page_id, lru_priority); + return this->pin_allocated_page_to_cache(device_entry, *page_id, options); } - if (wait_for_resource == batt::WaitForResource::kFalse) { + if (options.wait_for_resource() == batt::WaitForResource::kFalse) { break; } } - LLFS_LOG_WARNING() << "No arena with free space could be found;" << BATT_INSPECT(page_size) - << BATT_INSPECT(wait_for_resource); + LLFS_LOG_WARNING() << "No arena with free space could be found;" << BATT_INSPECT(options); return Status{batt::StatusCode::kUnavailable}; // TODO [tastolfi 2021-10-20] } @@ -493,20 +475,29 @@ StatusOr PageCache::allocate_page_of_size_log2(PageSizeLog2 size_log //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // StatusOr PageCache::pin_allocated_page_to_cache(PageDeviceEntry* device_entry, - PageSize page_size, PageId page_id, - LruPriority lru_priority) + PageId page_id, + const PageAllocateOptions& options) { PageArena& arena = device_entry->arena; StatusOr> new_page_buffer = arena.device().prepare(page_id); BATT_REQUIRE_OK(new_page_buffer); + // Initialize the page header. + { + const std::shared_ptr& buffer = BATT_OK_RESULT_OR_PANIC(new_page_buffer); + BATT_CHECK_EQ(page_id, buffer->page_id()); + + PackedPageHeader* const header = mutable_page_header(buffer.get()); + header->layout_id = options.page_layout_id(); + } + NewPageView* p_new_page_view = nullptr; // PageDevice::prepare must be thread-safe. // StatusOr pinned_slot = device_entry->cache.find_or_insert( - page_id, page_size, lru_priority, + page_id, options, /*initialize=*/ [&new_page_buffer, &p_new_page_view](const PageCacheSlot::PinnedRef& pinned_slot) { // Create a NewPageView object as a placeholder so we can insert the new page into the @@ -750,7 +741,7 @@ Status PageCache::assign_paired_device(PageSize src_page_size, const PageDeviceP // StatusOr PageCache::allocate_paired_page_for(PageId page_id, const PageDevicePairing& pairing, - LruPriority lru_priority) + const PageAllocateOptions& options) { Optional paired_page_id = this->paired_page_id_for(page_id, pairing); if (!paired_page_id) { @@ -759,11 +750,9 @@ StatusOr PageCache::allocate_paired_page_for(PageId page_id, PageDeviceEntry* paired_device_entry = this->get_device_for_page(*paired_page_id); BATT_CHECK_NOT_NULLPTR(paired_device_entry); + BATT_CHECK_EQ(options.page_size(), paired_device_entry->page_size()); - PageDevice* paired_page_device = std::addressof(paired_device_entry->arena.device()); - - return this->pin_allocated_page_to_cache(paired_device_entry, paired_page_device->page_size(), - *paired_page_id, lru_priority); + return this->pin_allocated_page_to_cache(paired_device_entry, *paired_page_id, options); } //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - @@ -871,7 +860,12 @@ auto PageCache::find_page_in_cache(PageId page_id, const PageLoadOptions& option BATT_CHECK_NOT_NULLPTR(entry); return entry->cache.find_or_insert( - page_id, entry->page_size(), options.lru_priority(), + page_id, + PageCacheInsertOptions{ + entry->page_size(), + options.lru_priority(), + options.overcommit(), + }, [this, entry, &options](const PageCacheSlot::PinnedRef& pinned_slot) { entry->cache.metrics().miss_count.add(1); this->async_load_page_into_slot(pinned_slot, options.required_layout(), diff --git a/src/llfs/page_cache.hpp b/src/llfs/page_cache.hpp index 2428d8d..7729393 100644 --- a/src/llfs/page_cache.hpp +++ b/src/llfs/page_cache.hpp @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -164,18 +166,11 @@ class PageCache : public PageLoader std::unique_ptr new_job(); - StatusOr allocate_page_of_size(PageSize size, batt::WaitForResource wait_for_resource, - LruPriority lru_priority, u64 callers, u64 job_id, - const batt::CancelToken& cancel_token = None); + StatusOr allocate_page(const PageAllocateOptions& options, u64 callers, u64 job_id); - StatusOr allocate_page_of_size_log2(PageSizeLog2 size_log2, - batt::WaitForResource wait_for_resource, - LruPriority lru_priority, u64 callers, u64 job_id, - const batt::CancelToken& cancel_token = None); - - ExternalAllocation allocate_external(usize byte_size) + ExternalAllocation allocate_external(usize byte_size, PageCacheOvercommit& overcommit) { - return this->cache_slot_pool_->allocate_external(byte_size); + return this->cache_slot_pool_->allocate_external(byte_size, overcommit); } // Returns a page allocated via `allocate_page` to the free pool. This MUST be done before the @@ -218,11 +213,11 @@ class PageCache : public PageLoader Optional paired_page_id_for(PageId page_id, const PageDevicePairing& pairing) const; /** \brief Allocates a PageBuffer for the page paired to `page_id` under the specified pairing - * relationship; pins the new page to the cache (with the specified priority)and returns the + * relationship; pins the new page to the cache (with the specified priority) and returns the * resulting PinnedPage. */ StatusOr allocate_paired_page_for(PageId page_id, const PageDevicePairing& pairing, - LruPriority lru_priority); + const PageAllocateOptions& options); /** \brief Writes the specified paired page. This happens outside the normal transactional page * creation workflow. @@ -339,9 +334,8 @@ class PageCache : public PageLoader void index_device_entries_by_page_size() noexcept; //----- --- -- - - - - - StatusOr pin_allocated_page_to_cache(PageDeviceEntry* device_entry, - PageSize page_size, PageId page_id, - LruPriority lru_priority); + StatusOr pin_allocated_page_to_cache(PageDeviceEntry* device_entry, PageId page_id, + const PageAllocateOptions& options); //----- --- -- - - - - /** \brief Attempts to find the specified page (`page_id`) in the cache; if successful, the cache diff --git a/src/llfs/page_cache_insert_options.hpp b/src/llfs/page_cache_insert_options.hpp new file mode 100644 index 0000000..367a328 --- /dev/null +++ b/src/llfs/page_cache_insert_options.hpp @@ -0,0 +1,133 @@ +//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ +// +// Part of the LLFS Project, under Apache License v2.0. +// See https://www.apache.org/licenses/LICENSE-2.0 for license information. +// SPDX short identifier: Apache-2.0 +// +//+++++++++++-+-+--+----- --- -- - - - - + +#pragma once +#define LLFS_PAGE_CACHE_INSERT_OPTIONS_HPP + +#include +// +#include +#include +#include + +#include +#include +#include + +#include + +namespace llfs { + +struct PageCacheInsertOptions { + using Self = PageCacheInsertOptions; + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief The size of the page being inserted (bytes); must be a power of 2. + */ + PageSize page_size_{0}; + + /** \brief The eviction priority (lower == evict first). + */ + LruPriority lru_priority_{1}; + + /** \brief Whether to allow cache over-commit for this insertion. + */ + PageCacheOvercommit* overcommit_ = std::addressof(PageCacheOvercommit::not_allowed()); + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Constructs a PageCacheInsertOptions with default values. + */ + PageCacheInsertOptions() = default; + + /** \brief Constructs a copy of the given options. + */ + PageCacheInsertOptions(const PageCacheInsertOptions&) = default; + + /** \brief Copies the passed options to *this, overwriting all set values. + */ + PageCacheInsertOptions& operator=(const PageCacheInsertOptions&) = default; + + /** \brief Constructs a PageCacheInsertOptions object from typed arguments; these may be in any + * order. + */ + template > + explicit PageCacheInsertOptions(Args&&... args) noexcept + : page_size_{batt::get_typed_arg(PageSize{0}, BATT_FORWARD(args)...)} + , lru_priority_{batt::get_typed_arg(LruPriority{1}, BATT_FORWARD(args)...)} + , overcommit_{std::addressof(batt::get_typed_arg( + PageCacheOvercommit::not_allowed(), BATT_FORWARD(args)...))} + { + static_assert(decltype(batt::has_typed_arg(BATT_FORWARD(args)...)){}); + static_assert(decltype(batt::has_typed_arg(BATT_FORWARD(args)...)){}); + } + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Returns a copy of this. + */ + Self clone() const + { + return *this; + } + + //----- --- -- - - - - + + Self& page_size(u32 n_bytes) + { + BATT_CHECK_EQ(batt::bit_count(n_bytes), 1) << "PageSize must be a power of 2"; + this->page_size_ = PageSize{n_bytes}; + return *this; + } + + PageSize page_size() const + { + return this->page_size_; + } + + //----- --- -- - - - - + + /** \brief Sets the LruPriority for updating the latest access field of the cache slot. + * + * This is an integer which is added to the current clock counter of the slot; each time the clock + * hand passes the slot, this counter is decremented. It must go to zero before the slot can be + * evicted. Therefore setting a higher priority value for certain slots will cause them to stay + * in cache longer. + */ + Self& lru_priority(i64 value) + { + this->lru_priority_ = LruPriority{value}; + return *this; + } + + /** \brief Returns the requested LruPriority for the load. + */ + LruPriority lru_priority() const + { + return this->lru_priority_; + } + + //----- --- -- - - - - + + /** \brief Sets the overcommit option for this load. + */ + Self& overcommit(PageCacheOvercommit& overcommit) + { + this->overcommit_ = std::addressof(overcommit); + return *this; + } + + PageCacheOvercommit& overcommit() const + { + BATT_CHECK_NOT_NULLPTR(this->overcommit_); + return *this->overcommit_; + } +}; + +} // namespace llfs diff --git a/src/llfs/page_cache_job.cpp b/src/llfs/page_cache_job.cpp index 5a7cfc2..0ee35e4 100644 --- a/src/llfs/page_cache_job.cpp +++ b/src/llfs/page_cache_job.cpp @@ -104,15 +104,23 @@ bool PageCacheJob::is_page_new_and_pinned(PageId page_id) const //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // StatusOr> PageCacheJob::new_page( - PageSize size, batt::WaitForResource wait_for_resource, const PageLayoutId& layout_id, - LruPriority lru_priority, u64 callers, const batt::CancelToken& cancel_token) + PageSize size, batt::WaitForResource wait_for_resource, llfs::PageCacheOvercommit& overcommit, + const PageLayoutId& layout_id, LruPriority lru_priority, u64 callers, + const batt::CancelToken& cancel_token) { // TODO [tastolfi 2021-04-07] instead of WaitForResource::kTrue, implement a backoff-and-retry // loop with a cancel token. // - StatusOr pinned_page = this->cache_->allocate_page_of_size( - size, wait_for_resource, lru_priority, callers | Caller::PageCacheJob_new_page, this->job_id, - cancel_token); + StatusOr pinned_page = this->cache_->allocate_page( + PageAllocateOptions{ + batt::make_copy(size), + batt::make_copy(wait_for_resource), + batt::make_copy(lru_priority), + batt::make_copy(layout_id), + overcommit, + cancel_token, + }, + callers | Caller::PageCacheJob_new_page, this->job_id); BATT_REQUIRE_OK(pinned_page); BATT_CHECK(*pinned_page); @@ -121,10 +129,6 @@ StatusOr> PageCacheJob::new_page( BATT_OK_RESULT_OR_PANIC(pinned_page->get()->get_new_page_buffer()); const PageId page_id = buffer->page_id(); - { - PackedPageHeader* const header = mutable_page_header(buffer.get()); - header->layout_id = layout_id; - } this->pruned_ = false; this->new_pages_.emplace(page_id, NewPage{std::move(*pinned_page), IsRecoveredPage{false}}); diff --git a/src/llfs/page_cache_job.hpp b/src/llfs/page_cache_job.hpp index f8e5ae1..bb59431 100644 --- a/src/llfs/page_cache_job.hpp +++ b/src/llfs/page_cache_job.hpp @@ -137,10 +137,21 @@ class PageCacheJob : public PageLoader // StatusOr> new_page(PageSize size, batt::WaitForResource wait_for_resource, + llfs::PageCacheOvercommit& overcommit, const PageLayoutId& layout_id, LruPriority lru_priority, u64 callers, const batt::CancelToken& cancel_token); + StatusOr> new_page(PageSize size, + batt::WaitForResource wait_for_resource, + const PageLayoutId& layout_id, + LruPriority lru_priority, u64 callers, + const batt::CancelToken& cancel_token) + { + return this->new_page(size, wait_for_resource, llfs::PageCacheOvercommit::not_allowed(), + layout_id, lru_priority, callers, cancel_token); + } + // Inserts a new page into the cache. The passed PageView must have been created using a // PageBuffer returned by `new_page` for this job, or we will panic. // diff --git a/src/llfs/page_cache_overcommit.hpp b/src/llfs/page_cache_overcommit.hpp new file mode 100644 index 0000000..c5200bd --- /dev/null +++ b/src/llfs/page_cache_overcommit.hpp @@ -0,0 +1,68 @@ +//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ +// +// Part of the LLFS Project, under Apache License v2.0. +// See https://www.apache.org/licenses/LICENSE-2.0 for license information. +// SPDX short identifier: Apache-2.0 +// +//+++++++++++-+-+--+----- --- -- - - - - + +#pragma once +#define LLFS_PAGE_CACHE_OVERCOMMIT_HPP + +#include +// + +namespace llfs { + +/** \brief Controls whether a given cache insertion is allowed to exceed the cache size limit. + * + * In certain cases, allowing a cache over-commit is preferable to deadlock. This mechanism + * allows the application to specify whether over-commit will be allowed for a given cache + * operation, and if so, whether the over-commit was triggered. + */ +class PageCacheOvercommit +{ + public: + using Self = PageCacheOvercommit; + + //+++++++++++-+-+--+----- --- -- - - - - + + static Self& not_allowed() + { + static Self obj_; + return obj_; + } + + //+++++++++++-+-+--+----- --- -- - - - - + + PageCacheOvercommit() = default; + + PageCacheOvercommit(const PageCacheOvercommit&) = delete; + PageCacheOvercommit& operator=(const PageCacheOvercommit&) = delete; + + bool is_allowed() const + { + return this->allowed_; + } + + bool is_triggered() const + { + return this->triggered_; + } + + void allow(bool b = true) + { + this->allowed_ = b; + } + + void trigger(bool b = true) + { + this->triggered_ = b; + } + + private: + bool allowed_ = false; + bool triggered_ = false; +}; + +} //namespace llfs diff --git a/src/llfs/page_cache_slot_pool.cpp b/src/llfs/page_cache_slot_pool.cpp index ebe29b3..4704dfe 100644 --- a/src/llfs/page_cache_slot_pool.cpp +++ b/src/llfs/page_cache_slot_pool.cpp @@ -115,9 +115,12 @@ PageCacheSlot* PageCacheSlot::Pool::get_slot(usize i) //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -auto PageCacheSlot::Pool::allocate(PageSize page_size) +auto PageCacheSlot::Pool::allocate(const PageCacheInsertOptions& options) -> std::tuple { + const PageSize page_size = options.page_size(); + PageCacheOvercommit& overcommit = options.overcommit(); + this->metrics_.allocate_count.add(1); const i64 max_resident_size = static_cast(this->max_byte_size_); @@ -144,10 +147,10 @@ auto PageCacheSlot::Pool::allocate(PageSize page_size) // Free queue is empty; if we can construct a new one, do it. // - const auto try_construct_new_slot = [this, prior_resident_size, - max_resident_size](PageCacheSlot** p_free_slot) { + const auto try_construct_new_slot = [this, prior_resident_size, max_resident_size, + &overcommit](PageCacheSlot** p_free_slot) { if (!*p_free_slot && this->n_allocated_.load() < this->n_slots_ && - prior_resident_size < max_resident_size) { + (prior_resident_size < max_resident_size || overcommit.is_allowed())) { *p_free_slot = this->construct_new_slot(); if (*p_free_slot) { BATT_CHECK(!(*p_free_slot)->is_valid()); @@ -169,28 +172,78 @@ auto PageCacheSlot::Pool::allocate(PageSize page_size) // the limit. // usize slot_i = 0; + usize try_construct_count = 0; + + bool first_pass = true; + usize unpinned_this_pass = 0; + usize evictions_this_pass = 0; + for (usize n_attempts = 0;; ++n_attempts) { const usize prev_slot_i = slot_i; slot_i = this->advance_clock_hand(n_slots_constructed); + const bool wrap_around = (slot_i < prev_slot_i); + // If we wrap-around and have no free slot, try constructing a new one if possible. // - if (!free_slot && (slot_i < prev_slot_i || n_attempts > this->n_slots_ * 2)) { + if (!free_slot && (wrap_around || n_attempts > this->n_slots_ * 2)) { + ++try_construct_count; try_construct_new_slot(&free_slot); - if (free_slot && observed_resident_size <= max_resident_size) { + if (free_slot) { + if (observed_resident_size <= max_resident_size) { + break; + } + } + } + + // If overcommit has already been triggered, then we don't impose a minimum number of + // attempts; just try to get through the bottleneck as quickly as possible. + // + if (free_slot && overcommit.is_allowed() && overcommit.is_triggered()) { + overcommit.trigger(); + break; + } + + // If overcommit is allowed and we have just completed a pass without making progress, then + // just trigger overcommit and return. + // + if (wrap_around) { + n_slots_constructed = this->n_constructed_.load(); + const bool making_progress = (evictions_this_pass != 0) || (unpinned_this_pass != 0); + const bool enough_effort = (n_attempts > n_slots_constructed * 16); + + if (free_slot && overcommit.is_allowed() && + ((!first_pass && !making_progress) || enough_effort)) { + overcommit.trigger(); break; } + //----- --- -- - - - - + first_pass = false; + evictions_this_pass = 0; + unpinned_this_pass = 0; } + LOG_IF_EVERY_N(INFO, n_attempts > 100000, 10000000) + << std::endl + << " slot_i: " << prev_slot_i << "->" << slot_i << BATT_INSPECT(try_construct_count) + << BATT_INSPECT(n_attempts) << BATT_INSPECT(n_slots_constructed) << "/" << this->n_slots_ + << BATT_INSPECT(overcommit.is_allowed()) << BATT_INSPECT(overcommit.is_triggered()) + << BATT_INSPECT((bool)free_slot) << BATT_INSPECT(first_pass) + << BATT_INSPECT(unpinned_this_pass) << BATT_INSPECT(evictions_this_pass); + // Try to expire the next slot; if that succeeds, try evicting. // PageCacheSlot* candidate = p_slots[slot_i].get(); if (!candidate->expire()) { + if (!candidate->is_pinned()) { + ++unpinned_this_pass; + } continue; } if (!candidate->evict()) { continue; } + ++evictions_this_pass; // If we don't have a slot to return yet, take this one; else add the just-evicted slot to // the free list so other threads can pick it up immediately. @@ -318,7 +371,7 @@ bool PageCacheSlot::Pool::push_free_slot(PageCacheSlot* slot) //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -Status PageCacheSlot::Pool::set_max_byte_size(usize new_size_limit) +Status PageCacheSlot::Pool::set_max_byte_size(usize new_size_limit, PageCacheOvercommit& overcommit) { const usize old_max_byte_size = this->max_byte_size_.exchange(new_size_limit); if (old_max_byte_size == new_size_limit) { @@ -328,7 +381,7 @@ Status PageCacheSlot::Pool::set_max_byte_size(usize new_size_limit) const usize n_slots_constructed = this->n_constructed_.load(); const usize max_steps = n_slots_constructed * 4; - Status status = this->enforce_max_size(this->resident_size_.load(), max_steps); + Status status = this->enforce_max_size(this->resident_size_.load(), overcommit, max_steps); if (!status.ok()) { usize expected = new_size_limit; do { @@ -345,22 +398,24 @@ Status PageCacheSlot::Pool::set_max_byte_size(usize new_size_limit) //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -auto PageCacheSlot::Pool::allocate_external(usize byte_size) -> ExternalAllocation +auto PageCacheSlot::Pool::allocate_external(usize byte_size, PageCacheOvercommit& overcommit) + -> ExternalAllocation { if (byte_size != 0) { BATT_CHECK_LE(byte_size, this->max_byte_size_); const i64 prior_resident_size = this->resident_size_.fetch_add(byte_size); - i64 observed_resident_size = prior_resident_size + byte_size; + const i64 observed_resident_size = prior_resident_size + byte_size; - BATT_CHECK_OK(this->enforce_max_size(observed_resident_size)); + BATT_CHECK_OK(this->enforce_max_size(observed_resident_size, overcommit)); } return ExternalAllocation{*this, byte_size}; } //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -Status PageCacheSlot::Pool::enforce_max_size(i64 observed_resident_size, usize max_steps) +Status PageCacheSlot::Pool::enforce_max_size(i64 observed_resident_size, + PageCacheOvercommit& overcommit, usize max_steps) { const i64 max_resident_size = static_cast(this->max_byte_size_); @@ -368,23 +423,57 @@ Status PageCacheSlot::Pool::enforce_max_size(i64 observed_resident_size, usize m usize n_slots_constructed = this->n_constructed_.load(); batt::CpuCacheLineIsolated* const p_slots = this->slots(); + if (overcommit.is_allowed() && overcommit.is_triggered()) { + return OkStatus(); + } + + usize slot_i = 0; usize step_count = 0; + + bool first_pass = true; + usize unpinned_this_pass = 0; + usize evictions_this_pass = 0; + while (observed_resident_size > max_resident_size) { ++step_count; if (max_steps && step_count > max_steps) { + if (overcommit.is_allowed()) { + overcommit.trigger(); + return OkStatus(); + } return batt::StatusCode::kResourceExhausted; } // Try to expire the next slot; if that succeeds, try evicting. // - const usize slot_i = this->advance_clock_hand(n_slots_constructed); + const usize prev_slot_i = slot_i; + slot_i = this->advance_clock_hand(n_slots_constructed); + + const bool wrap_around = (slot_i < prev_slot_i); + if (wrap_around) { + const bool making_progress = (evictions_this_pass != 0) || (unpinned_this_pass != 0); + if (overcommit.is_allowed() && !first_pass && !making_progress) { + overcommit.trigger(); + break; + } + //----- --- -- - - - - + first_pass = false; + evictions_this_pass = 0; + unpinned_this_pass = 0; + } + PageCacheSlot* candidate = p_slots[slot_i].get(); if (!candidate->expire()) { + if (!candidate->is_pinned()) { + ++unpinned_this_pass; + } continue; } if (!candidate->evict()) { continue; } + ++evictions_this_pass; + candidate->clear(); this->push_free_slot(candidate); observed_resident_size = this->resident_size_.load(); diff --git a/src/llfs/page_cache_slot_pool.hpp b/src/llfs/page_cache_slot_pool.hpp index f0d2834..1358ce0 100644 --- a/src/llfs/page_cache_slot_pool.hpp +++ b/src/llfs/page_cache_slot_pool.hpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include #include @@ -177,7 +179,19 @@ class PageCacheSlot::Pool : public boost::intrusive_ref_counter * Thereafter, it will attempt to evict an unpinned slot that hasn't been used recently. If no * such slot can be found, `nullptr` will be returned. */ - auto allocate(PageSize size_needed) -> std::tuple; + auto allocate(const PageCacheInsertOptions& options) + -> std::tuple; + + /** \brief Allocate a slot; over-commit is not allowed. + */ + auto allocate(PageSize size_needed) -> std::tuple + { + return this->allocate(PageCacheInsertOptions{ + batt::make_copy(size_needed), + LruPriority{0}, + PageCacheOvercommit::not_allowed(), + }); + } /** \brief Returns the index of the specified slot object. * @@ -231,7 +245,7 @@ class PageCacheSlot::Pool : public boost::intrusive_ref_counter * unable to shrink the cache to the desired limit. If this happens, the size limit will not be * changed, and `batt::StatusCode::kResourceExhausted` will be returned. */ - Status set_max_byte_size(usize new_size_limit); + Status set_max_byte_size(usize new_size_limit, PageCacheOvercommit& overcommit); /** \brief Allocates against the maximum byte size limit, without explicitly allocating or filling * any cache slots. @@ -240,7 +254,7 @@ class PageCacheSlot::Pool : public boost::intrusive_ref_counter * page cache quota for external use. The returned object is a move-only RAII type that returns * the allocated amount to the cache when the last (moved) copy is destructed. */ - ExternalAllocation allocate_external(usize byte_size); + ExternalAllocation allocate_external(usize byte_size, PageCacheOvercommit& overcommit); //+++++++++++-+-+--+----- --- -- - - - - private: @@ -266,7 +280,8 @@ class PageCacheSlot::Pool : public boost::intrusive_ref_counter /** \brief If the passed observed size is over the limit, evict pages until resident size is at or * below the maximum size. */ - Status enforce_max_size(i64 observed_resident_size, usize max_steps = 0); + Status enforce_max_size(i64 observed_resident_size, PageCacheOvercommit& overcommit, + usize max_steps = 0); //+++++++++++-+-+--+----- --- -- - - - - diff --git a/src/llfs/page_device_cache.cpp b/src/llfs/page_device_cache.cpp index 1591dc1..3120c1d 100644 --- a/src/llfs/page_device_cache.cpp +++ b/src/llfs/page_device_cache.cpp @@ -52,9 +52,12 @@ const PageIdFactory& PageDeviceCache::page_ids() const noexcept //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // batt::StatusOr PageDeviceCache::find_or_insert( - PageId key, PageSize page_size, LruPriority lru_priority, + PageId key, const PageCacheInsertOptions& options, const batt::SmallFn& initialize) { + const PageSize page_size = options.page_size(); + const LruPriority lru_priority = options.lru_priority(); + static const bool kEvictOldGenSlot = batt::getenv_as("LLFS_EVICT_OLD_GEN_SLOT").value_or(true); @@ -148,7 +151,7 @@ batt::StatusOr PageDeviceCache::find_or_insert( PageCacheSlot::ExternalAllocation claim; new_slot.emplace(); - std::tie(new_slot->p_slot, claim) = this->slot_pool_->allocate(page_size); + std::tie(new_slot->p_slot, claim) = this->slot_pool_->allocate(options); if (!new_slot->p_slot) { this->metrics().full_count.add(1); return ::llfs::make_status(StatusCode::kCacheSlotsFull); diff --git a/src/llfs/page_device_cache.hpp b/src/llfs/page_device_cache.hpp index cbce827..7477b78 100644 --- a/src/llfs/page_device_cache.hpp +++ b/src/llfs/page_device_cache.hpp @@ -14,6 +14,7 @@ // #include #include +#include #include #include #include @@ -74,7 +75,7 @@ class PageDeviceCache * called to start the process of loading the page data into the slot. */ batt::StatusOr find_or_insert( - PageId key, PageSize page_size, LruPriority lru_priority, + PageId key, const PageCacheInsertOptions& options, const batt::SmallFn& initialize); /** \brief Attempt to find and pin the given page in the cache. No attempt to load the page will diff --git a/src/llfs/page_load_options.hpp b/src/llfs/page_load_options.hpp new file mode 100644 index 0000000..45850ee --- /dev/null +++ b/src/llfs/page_load_options.hpp @@ -0,0 +1,197 @@ +//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ +// +// Part of the LLFS Project, under Apache License v2.0. +// See https://www.apache.org/licenses/LICENSE-2.0 for license information. +// SPDX short identifier: Apache-2.0 +// +//+++++++++++-+-+--+----- --- -- - - - - + +#pragma once +#define LLFS_PAGE_LOAD_OPTIONS_HPP + +#include +// +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace llfs { + +/** \brief Parameters that control how a page is loaded. + */ +struct PageLoadOptions { + using Self = PageLoadOptions; + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief The default is not to require a specific data layout. + */ + Optional required_layout_ = None; + + /** \brief The default is to let the loader/job decide whether to pin the page to itself. + */ + PinPageToJob pin_page_to_job_ = PinPageToJob::kDefault; + + /** \brief The default is higher verbosity for diagnostics if the page is not found. + */ + OkIfNotFound ok_if_not_found_ = OkIfNotFound{false}; + + /** \brief The default is to set the latest use counter of the loaded page's cache slot to 1. + */ + LruPriority lru_priority_ = LruPriority{1}; + + /** \brief Whether to allow cache over-commit for this load. + */ + PageCacheOvercommit* overcommit_ = std::addressof(PageCacheOvercommit::not_allowed()); + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Constructs a PageLoadOptions with default values. + */ + PageLoadOptions() = default; + + /** \brief Constructs a copy of the given options. + */ + PageLoadOptions(const PageLoadOptions&) = default; + + /** \brief Copies the passed options to *this, overwriting all set values. + */ + PageLoadOptions& operator=(const PageLoadOptions&) = default; + + /** \brief Constructs a PageLoadOptions object from typed arguments; these may be in any order. + */ + template > + explicit PageLoadOptions(Args&&... args) noexcept; + + //+++++++++++-+-+--+----- --- -- - - - - + + /** \brief Returns a copy of this. + */ + Self clone() const + { + return *this; + } + + //----- --- -- - - - - + + /** \brief Requires that the loaded page be of the given data layout. + */ + Self& required_layout(const Optional& value) + { + this->required_layout_ = value; + return *this; + } + + /** \brief Returns the required data layout for the page. + */ + const Optional& required_layout() const + { + return this->required_layout_; + } + + //----- --- -- - - - - + + /** \brief Sets whether the page should be pinned to the conetxt of the loader/job. + */ + Self& pin_page_to_job(PinPageToJob value) + { + this->pin_page_to_job_ = value; + return *this; + } + + /** \brief Sets whether the page should be pinned to the conetxt of the loader/job. + */ + Self& pin_page_to_job(bool value) + { + this->pin_page_to_job_ = value ? PinPageToJob::kTrue : PinPageToJob::kFalse; + return *this; + } + + /** \brief Returns whether the page is to be pinned to the context of the loader/job. + */ + PinPageToJob pin_page_to_job() const + { + return this->pin_page_to_job_; + } + + //----- --- -- - - - - + + /** \brief Sets whether the caller considers it valid for the page not to be found; this controls + * the verbosity of error diagnostics. + */ + Self& ok_if_not_found(bool value) + { + this->ok_if_not_found_ = OkIfNotFound{value}; + return *this; + } + + /** \brief Returns whether to print extra diagnostics if the page is not found. + */ + OkIfNotFound ok_if_not_found() const + { + return this->ok_if_not_found_; + } + + //----- --- -- - - - - + + /** \brief Sets the LruPriority for updating the latest access field of the cache slot. + * + * This is an integer which is added to the current clock counter of the slot; each time the clock + * hand passes the slot, this counter is decremented. It must go to zero before the slot can be + * evicted. Therefore setting a higher priority value for certain slots will cause them to stay + * in cache longer. + */ + Self& lru_priority(i64 value) + { + this->lru_priority_ = LruPriority{value}; + return *this; + } + + /** \brief Returns the requested LruPriority for the load. + */ + LruPriority lru_priority() const + { + return this->lru_priority_; + } + + //----- --- -- - - - - + + /** \brief Sets the overcommit option for this load. + */ + Self& overcommit(PageCacheOvercommit& overcommit) + { + this->overcommit_ = std::addressof(overcommit); + return *this; + } + + PageCacheOvercommit& overcommit() const + { + BATT_CHECK_NOT_NULLPTR(this->overcommit_); + return *this->overcommit_; + } +}; + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - +// +template +inline /*explicit*/ PageLoadOptions::PageLoadOptions(Args&&... args) noexcept + : required_layout_{batt::get_typed_arg>(None, args...)} + , pin_page_to_job_{batt::get_typed_arg(PinPageToJob::kDefault, args...)} + , ok_if_not_found_{batt::get_typed_arg(OkIfNotFound{false}, args...)} + , lru_priority_{batt::get_typed_arg(LruPriority{1}, args...)} + , overcommit_{std::addressof( + batt::get_typed_arg(PageCacheOvercommit::not_allowed(), args...))} +{ +} + +} //namespace llfs diff --git a/src/llfs/page_load_options.test.cpp b/src/llfs/page_load_options.test.cpp new file mode 100644 index 0000000..9a698c2 --- /dev/null +++ b/src/llfs/page_load_options.test.cpp @@ -0,0 +1,39 @@ +//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ +// +// Part of the LLFS Project, under Apache License v2.0. +// See https://www.apache.org/licenses/LICENSE-2.0 for license information. +// SPDX short identifier: Apache-2.0 +// +//+++++++++++-+-+--+----- --- -- - - - - + +#include +// +#include + +#include +#include + +namespace { + +TEST(PageLoadOptionsTest, PageCacheOvercommit) +{ + llfs::PageCacheOvercommit overcommit; + overcommit.allow(true); + { + llfs::PageLoadOptions load_options{llfs::LruPriority{1}, overcommit}; + EXPECT_EQ(std::addressof(overcommit), std::addressof(load_options.overcommit())); + } + { + llfs::PageLoadOptions load_options{overcommit, llfs::LruPriority{1}}; + EXPECT_EQ(std::addressof(overcommit), std::addressof(load_options.overcommit())); + } + { + llfs::PageLoadOptions load_options{}; + + EXPECT_NE(std::addressof(overcommit), std::addressof(load_options.overcommit())); + EXPECT_EQ(std::addressof(llfs::PageCacheOvercommit::not_allowed()), + std::addressof(load_options.overcommit())); + } +} + +} // namespace diff --git a/src/llfs/page_loader.hpp b/src/llfs/page_loader.hpp index dd0cadf..03d9163 100644 --- a/src/llfs/page_loader.hpp +++ b/src/llfs/page_loader.hpp @@ -12,162 +12,18 @@ #include // -#include -#include #include #include #include +#include #include #include #include -#include -#include - namespace llfs { class PageCache; -/** \brief Parameters that control how a page is loaded. - */ -struct PageLoadOptions { - using Self = PageLoadOptions; - - //+++++++++++-+-+--+----- --- -- - - - - - - /** \brief The default is not to require a specific data layout. - */ - Optional required_layout_ = None; - - /** \brief The default is to let the loader/job decide whether to pin the page to itself. - */ - PinPageToJob pin_page_to_job_ = PinPageToJob::kDefault; - - /** \brief The default is higher verbosity for diagnostics if the page is not found. - */ - OkIfNotFound ok_if_not_found_ = OkIfNotFound{false}; - - /** \brief The default is to set the latest use counter of the loaded page's cache slot to 1. - */ - LruPriority lru_priority_ = LruPriority{1}; - - //+++++++++++-+-+--+----- --- -- - - - - - - /** \brief Constructs a PageLoadOptions with default values. - */ - PageLoadOptions() = default; - - /** \brief Constructs a copy of the given options. - */ - PageLoadOptions(const PageLoadOptions&) = default; - - /** \brief Copies the passed options to *this, overwriting all set values. - */ - PageLoadOptions& operator=(const PageLoadOptions&) = default; - - /** \brief Constructs a PageLoadOptions object from typed arguments; these may be in any order. - */ - template > - explicit PageLoadOptions(Args&&... args) noexcept - : required_layout_{batt::get_typed_arg>(None, args...)} - , pin_page_to_job_{batt::get_typed_arg(PinPageToJob::kDefault, args...)} - , ok_if_not_found_{batt::get_typed_arg(OkIfNotFound{false}, args...)} - , lru_priority_{batt::get_typed_arg(LruPriority{1}, args...)} - { - } - - //+++++++++++-+-+--+----- --- -- - - - - - - /** \brief Returns a copy of this. - */ - Self clone() const - { - return *this; - } - - //----- --- -- - - - - - - /** \brief Requires that the loaded page be of the given data layout. - */ - Self& required_layout(const Optional& value) - { - this->required_layout_ = value; - return *this; - } - - /** \brief Returns the required data layout for the page. - */ - const Optional& required_layout() const - { - return this->required_layout_; - } - - //----- --- -- - - - - - - /** \brief Sets whether the page should be pinned to the conetxt of the loader/job. - */ - Self& pin_page_to_job(PinPageToJob value) - { - this->pin_page_to_job_ = value; - return *this; - } - - /** \brief Sets whether the page should be pinned to the conetxt of the loader/job. - */ - Self& pin_page_to_job(bool value) - { - this->pin_page_to_job_ = value ? PinPageToJob::kTrue : PinPageToJob::kFalse; - return *this; - } - - /** \brief Returns whether the page is to be pinned to the context of the loader/job. - */ - PinPageToJob pin_page_to_job() const - { - return this->pin_page_to_job_; - } - - //----- --- -- - - - - - - /** \brief Sets whether the caller considers it valid for the page not to be found; this controls - * the verbosity of error diagnostics. - */ - Self& ok_if_not_found(bool value) - { - this->ok_if_not_found_ = OkIfNotFound{value}; - return *this; - } - - /** \brief Returns whether to print extra diagnostics if the page is not found. - */ - OkIfNotFound ok_if_not_found() const - { - return this->ok_if_not_found_; - } - - //----- --- -- - - - - - - /** \brief Sets the LruPriority for updating the latest access field of the cache slot. - * - * This is an integer which is added to the current clock counter of the slot; each time the clock - * hand passes the slot, this counter is decremented. It must go to zero before the slot can be - * evicted. Therefore setting a higher priority value for certain slots will cause them to stay - * in cache longer. - */ - Self& lru_priority(i64 value) - { - this->lru_priority_ = LruPriority{value}; - return *this; - } - - /** \brief Returns the requested LruPriority for the load. - */ - LruPriority lru_priority() const - { - return this->lru_priority_; - } -}; - /** \brief Interface for an entity which can resolve `PageId`s into `PinnedPage`s. */ template