Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/storage/page_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
#include "storage/page_manager.h"

#include "common/exception/runtime.h"
#include "common/uniq_lock.h"
#include "storage/file_handle.h"
#include "storage/storage_manager.h"
#include <format>

namespace lbug::storage {
static constexpr bool ENABLE_FSM = true;

namespace {
// An allocated page index is turned straight into a file offset by the write path, so a range
// that does not belong to this file silently extends it rather than failing (issue #948: page
// 233,877,254 of a 2884-page database left an 11.8 MB file reporting 892 GiB of apparent size,
// which only surfaces once a copy or backup materialises the sparse hole). The free list is
// deserialized from the database file, so a corrupted entry lands here first: fail at the
// source, where the offending range can still be named.
//
// The file does shrink, via FreeSpaceManager::handleLastPageRange ->
// FileHandle::removePageIdxAndTruncateIfNecessary, but that path truncates to the start of the
// trailing free range and drops that range instead of re-adding it, and every surviving entry
// sorts below it, so a legitimate free entry stays inside the file.
void validateAllocatedPageRange(const PageRange& range, const FileHandle& fileHandle,
const char* source) {
if (range.numPages == 0) {
// A zero-page allocation writes nothing, and its start index legitimately sits at the
// current end of the file.
return;
}
const auto numPages = fileHandle.getNumPages();
if (range.startPageIdx >= numPages || range.numPages > numPages - range.startPageIdx) {
throw common::RuntimeException(
std::format("Page allocation from {} returned pages [{}, {}), which are out of "
"bounds for a data file with {} pages. The database file may be "
"corrupted.",
source, range.startPageIdx,
static_cast<uint64_t>(range.startPageIdx) + range.numPages, numPages));
}
}
} // namespace

PageRange PageManager::allocatePageRange(common::page_idx_t numPages) {
if constexpr (ENABLE_FSM) {
common::UniqLock lck{mtx};
auto allocatedFreeChunk = freeSpaceManager->popFreePages(numPages);
if (allocatedFreeChunk.has_value()) {
validateAllocatedPageRange(*allocatedFreeChunk, *fileHandle, "the free page list");
version.fetch_add(1, std::memory_order_relaxed);
return {*allocatedFreeChunk};
}
}
auto startPageIdx = fileHandle->addNewPages(numPages);
// DASSERT alone is stripped in release builds, and this invariant guards a file offset.
DASSERT(fileHandle->getNumPages() >= startPageIdx + numPages);
validateAllocatedPageRange(PageRange(startPageIdx, numPages), *fileHandle,
"the page extension path");
return PageRange(startPageIdx, numPages);
}

Expand Down
Loading