From 581c04d92971b45c3dec39d383d8912fbe0db903 Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Mon, 29 Sep 2025 15:03:51 +0300 Subject: [PATCH 1/7] refactor(pag): Use `fallocate` as the primary method for extending a file - This should eliminate double writes of pages when file is extended. - If fallocate is not supported by filesystem fallback to the old method of writing zeroes to the file. --- src/jrd/nbak.cpp | 8 +- src/jrd/os/pio_proto.h | 5 +- src/jrd/os/posix/unix.cpp | 26 +++-- src/jrd/os/win32/winnt.cpp | 12 ++- src/jrd/pag.cpp | 210 +++++++++++++++++++------------------ src/jrd/pag.h | 7 +- 6 files changed, 152 insertions(+), 116 deletions(-) diff --git a/src/jrd/nbak.cpp b/src/jrd/nbak.cpp index 399908b9a61..5ff95d6a8df 100644 --- a/src/jrd/nbak.cpp +++ b/src/jrd/nbak.cpp @@ -389,10 +389,12 @@ bool BackupManager::extendDatabase(thread_db* tdbb) if (maxAllocPage >= maxPage) return true; - if (!pgSpace->extend(tdbb, maxPage, true)) - return false; - + const auto extension_result = pgSpace->extend(tdbb, maxPage, true); maxAllocPage = pgSpace->maxAlloc(); + if (extension_result.success && maxAllocPage > maxPage) + return true; + + // Fast file extension not succeeded for some reason, try file extension via direct file writes. while (maxAllocPage < maxPage) { const USHORT ret = PIO_init_data(tdbb, pgSpace->file, tdbb->tdbb_status_vector, diff --git a/src/jrd/os/pio_proto.h b/src/jrd/os/pio_proto.h index b1deba54342..99a7dda760b 100644 --- a/src/jrd/os/pio_proto.h +++ b/src/jrd/os/pio_proto.h @@ -40,12 +40,13 @@ void PIO_close(Jrd::jrd_file*); Jrd::jrd_file* PIO_create(Jrd::thread_db*, const Firebird::PathName&, const bool, const bool); bool PIO_expand(const TEXT*, USHORT, TEXT*, FB_SIZE_T); -void PIO_extend(Jrd::thread_db*, Jrd::jrd_file*, const ULONG, const USHORT); +bool PIO_fast_extension_is_supported(const Jrd::jrd_file& file) noexcept; +bool PIO_extend(Jrd::thread_db* tdbb, Jrd::jrd_file* file, ULONG extPages, USHORT pageSize); void PIO_flush(Jrd::thread_db*, Jrd::jrd_file*); void PIO_force_write(Jrd::jrd_file*, const bool); ULONG PIO_get_number_of_pages(const Jrd::jrd_file*, const USHORT); bool PIO_header(Jrd::thread_db*, UCHAR*, unsigned); -USHORT PIO_init_data(Jrd::thread_db*, Jrd::jrd_file*, Jrd::FbStatusVector*, ULONG, USHORT); +USHORT PIO_init_data(Jrd::thread_db* tdbb, Jrd::jrd_file* file, Jrd::FbStatusVector* status_vector, ULONG startPage, USHORT initPages); Jrd::jrd_file* PIO_open(Jrd::thread_db*, const Firebird::PathName&, const Firebird::PathName&); bool PIO_read(Jrd::thread_db*, Jrd::jrd_file*, Jrd::BufferDesc*, Ods::pag*, Jrd::FbStatusVector*); diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index 232b1941c53..d62e0556c15 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -299,7 +299,19 @@ bool PIO_expand(const TEXT* file_name, USHORT file_length, TEXT* expanded_name, } -void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USHORT pageSize) +bool PIO_fast_extension_is_supported(const Jrd::jrd_file& file) noexcept +{ +#if defined(HAVE_LINUX_FALLOC_H) && defined(HAVE_FALLOCATE) + return file.fil_flags & FIL_no_fast_extend + ? false + : true; +#else + return false; +#endif +} + + +bool PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USHORT pageSize) { /************************************** * @@ -317,8 +329,8 @@ void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USH EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); - if (file->fil_flags & FIL_no_fast_extend) - return; + if (!PIO_fast_extension_is_supported(*file)) + return false; const ULONG filePages = PIO_get_number_of_pages(file, pageSize); const ULONG extendBy = MIN(MAX_ULONG - filePages, extPages); @@ -341,7 +353,7 @@ void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USH unix_error("fallocate", file, isc_io_write_err); file->fil_flags |= FIL_no_fast_extend; - return; + return false; } if (r == IO_RETRY) @@ -352,12 +364,12 @@ void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USH #endif unix_error("fallocate_retry", file, isc_io_write_err); } + + return true; #else file->fil_flags |= FIL_no_fast_extend; + return false; #endif // fallocate present - - // not implemented - return; } diff --git a/src/jrd/os/win32/winnt.cpp b/src/jrd/os/win32/winnt.cpp index e8507e021fa..a7733d59891 100644 --- a/src/jrd/os/win32/winnt.cpp +++ b/src/jrd/os/win32/winnt.cpp @@ -215,7 +215,13 @@ bool PIO_expand(const TEXT* file_name, USHORT file_length, TEXT* expanded_name, } -void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USHORT pageSize) +bool PIO_fast_extension_is_supported(const Jrd::jrd_file& file) noexcept +{ + return true; +} + + +bool PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USHORT pageSize) { /************************************** * @@ -238,7 +244,7 @@ void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USH // if file have no extend lock it is better to not extend file than corrupt it if (!file->fil_ext_lock) - return; + return false; EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); FileExtendLockGuard extLock(file->fil_ext_lock, true); @@ -257,6 +263,8 @@ void PIO_extend(thread_db* tdbb, jrd_file* file, const ULONG extPages, const USH if (!SetEndOfFile(hFile)) nt_error("SetEndOfFile", file, isc_io_write_err, NULL); + + return true; } diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index 2faa86e1f87..ccf7b611174 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -102,7 +102,7 @@ using namespace Firebird; namespace { constexpr const char* const SCRATCH = "fb_table_"; - constexpr int MIN_EXTEND_BYTES = 128 * 1024; // 128KB + constexpr ULONG MIN_EXTEND_BYTES = 128 * 1024; // 128KiB inline void ensureDbWritable(thread_db* tdbb) { @@ -266,99 +266,94 @@ namespace CCH_RELEASE(tdbb, &window); } - ULONG ensureDiskSpace(thread_db* tdbb, WIN* pip_window, const PageNumber pageNum, ULONG pipUsed) + ULONG ensureDiskSpace(thread_db* tdbb, WIN* pip_window, const PageNumber page_num, const ULONG pip_used) { const auto* dbb = tdbb->getDatabase(); const PageManager& pageMgr = dbb->dbb_page_manager; - PageSpace* const pageSpace = pageMgr.findPageSpace(pageNum.getPageSpaceID()); + PageSpace* const pageSpace = pageMgr.findPageSpace(page_num.getPageSpaceID()); - ULONG newUsed = pipUsed; - const ULONG sequence = pageNum.getPageNum() / pageMgr.pagesPerPIP; - const ULONG relative_bit = pageNum.getPageNum() - sequence * pageMgr.pagesPerPIP; + const ULONG sequence = page_num.getPageNum() / pageMgr.pagesPerPIP; + const ULONG relative_bit = page_num.getPageNum() - sequence * pageMgr.pagesPerPIP; + + if (relative_bit + 1 <= pip_used) + return pip_used; + fb_assert(relative_bit >= pip_used); BackupManager::StateReadGuard stateGuard(tdbb); - const bool nbak_stalled = dbb->dbb_backup_manager->getState() == Ods::hdr_nbak_stalled; - USHORT next_init_pages = 1; - // ensure there are space on disk for faked page - if (relative_bit + 1 > pipUsed) + if (dbb->dbb_backup_manager->getState() == Ods::hdr_nbak_stalled) { - fb_assert(relative_bit >= pipUsed); + // Our file is locked, so we can't extend it anyway. + // Delta will be extended via simple `write` syscalls. + return relative_bit + 1; + } - USHORT init_pages = 0; - if (!nbak_stalled) - { - init_pages = 1; - if (!(dbb->dbb_flags & DBB_no_reserve)) - { - const int minExtendPages = MIN_EXTEND_BYTES / dbb->dbb_page_size; + USHORT init_pages = 1; - init_pages = sequence ? 64 : MIN(pipUsed / 16, 64); + if (!(dbb->dbb_flags & DBB_no_reserve)) + { + const unsigned min_extend_pages = MIN_EXTEND_BYTES / dbb->dbb_page_size; - // don't touch pages belongs to the next PIP - init_pages = MIN(init_pages, pageMgr.pagesPerPIP - pipUsed); + constexpr ULONG MAX_PAGES_IN_EXTENT = 64; + init_pages = sequence ? MAX_PAGES_IN_EXTENT : MIN(pip_used / 16, MAX_PAGES_IN_EXTENT); - if (init_pages < minExtendPages) - init_pages = 1; - } + // don't touch pages belongs to the next PIP + init_pages = MIN(init_pages, pageMgr.pagesPerPIP - pip_used); - if (init_pages < relative_bit + 1 - pipUsed) - init_pages = relative_bit + 1 - pipUsed; + if (init_pages < min_extend_pages) + init_pages = 1; + } - //init_pages = FB_ALIGN(init_pages, PAGES_IN_EXTENT); + if (init_pages < relative_bit + 1 - pip_used) + init_pages = relative_bit + 1 - pip_used; - next_init_pages = init_pages; + // init_pages = FB_ALIGN(init_pages, PAGES_IN_EXTENT); - FbLocalStatus status; - const ULONG start = sequence * pageMgr.pagesPerPIP + pipUsed; + FbLocalStatus status; + const ULONG used_pages = sequence * pageMgr.pagesPerPIP + pip_used; - init_pages = PIO_init_data(tdbb, pageSpace->file, &status, start, init_pages); - } + const bool allocate_exact_number_of_pages = dbb->dbb_flags & DBB_no_reserve; + const auto extension_result = pageSpace->extend(tdbb, used_pages + init_pages, allocate_exact_number_of_pages); + if (extension_result.success) + init_pages = extension_result.pages_allocated; + else + { + // For some reason fast file extension failed, maybe it is not supported by filesystem, or + // there is not enough space. Try the old way with writing zeroes to extend file. + init_pages = PIO_init_data(tdbb, pageSpace->file, &status, used_pages, init_pages); + } - if (init_pages) + if (!init_pages) + { + // Zero pages was allocated - perhaps it is not supported, + // no space left on disk or IO error occurred. Try to write + // one page and handle IO errors if any. + WIN window(page_num); + CCH_fake(tdbb, &window, 1); + CCH_must_write(tdbb, &window); + try { - newUsed += init_pages; + CCH_RELEASE(tdbb, &window); } - else + catch (const status_exception&) { - // PIO_init_data returns zero - perhaps it is not supported, - // no space left on disk or IO error occurred. Try to write - // one page and handle IO errors if any. - WIN window(pageNum); - CCH_fake(tdbb, &window, 1); - CCH_must_write(tdbb, &window); - try - { - CCH_RELEASE(tdbb, &window); - } - catch (const status_exception&) - { - // forget about this page as if we never tried to fake it - CCH_forget_page(tdbb, &window); - - // normally all page buffers now released by CCH_unwind - // only exception is when TDBB_no_cache_unwind flag is set - if (tdbb->tdbb_flags & TDBB_no_cache_unwind) - CCH_RELEASE(tdbb, pip_window); + // forget about this page as if we never tried to fake it + CCH_forget_page(tdbb, &window); - throw; - } + // normally all page buffers now released by CCH_unwind + // only exception is when TDBB_no_cache_unwind flag is set + if (tdbb->tdbb_flags & TDBB_no_cache_unwind) + CCH_RELEASE(tdbb, pip_window); - newUsed = relative_bit + 1; + throw; } - } - if (!(dbb->dbb_flags & DBB_no_reserve) && !nbak_stalled) - { - const ULONG initialized = sequence * pageMgr.pagesPerPIP + pipUsed; - - // At this point we ensure database has at least "initialized" pages - // allocated. To avoid file growth by few pages when all this space - // will be used, extend file up to initialized + next_init_pages now - pageSpace->extend(tdbb, initialized + next_init_pages, false); + return relative_bit + 1; } - return newUsed; + // We can allocate more pages than we requested, so don't return value bigger + // than PIP can handle (`pagesPerPIP` is limit) + return MIN(pip_used + init_pages, pageMgr.pagesPerPIP); } } // namespace @@ -659,6 +654,7 @@ PAG PAG_allocate_pages(thread_db* tdbb, WIN* window, unsigned cntAlloc, bool ali pip_page->pip_min = pipMin; pip_page->pip_extent = pipExtent; + fb_assert(pipUsed <= pageMgr.pagesPerPIP); pip_page->pip_used = pipUsed; for (const ULONG *bit = extraPages.begin(); bit < extraPages.end(); bit++) @@ -1943,7 +1939,7 @@ ULONG PageSpace::usedPages(const Database* dbb) return pgSpace->usedPages(); } -bool PageSpace::extend(thread_db* tdbb, const ULONG pageNum, const bool forceSize) +PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) { /************************************** * @@ -1962,54 +1958,66 @@ bool PageSpace::extend(thread_db* tdbb, const ULONG pageNum, const bool forceSiz **************************************/ fb_assert(dbb == tdbb->getDatabase()); - const int MAX_EXTEND_BYTES = dbb->dbb_config->getDatabaseGrowthIncrement(); + if (!PIO_fast_extension_is_supported(*file)) + return {.success = false}; - if (pageNum < maxPageNumber || MAX_EXTEND_BYTES < MIN_EXTEND_BYTES && !forceSize) - return true; + // First, check it with the cached `maxPageNumber` value. + if (pageNum < maxPageNumber || pageNum < maxAlloc()) + return {.success = true, .pages_allocated = maxPageNumber - pageNum}; - if (pageNum >= maxAlloc()) + const ULONG MAX_EXTEND_BYTES = static_cast(dbb->dbb_config->getDatabaseGrowthIncrement()); + if (MAX_EXTEND_BYTES < MIN_EXTEND_BYTES) { + // Preallocation by extent is disabled, so only allocate the requested size. + forceSize = true; + } + + const ULONG reqPages = pageNum - maxPageNumber + 1; + + ULONG extPages = reqPages; + if (!forceSize) + { + // We can extend the file more than we requested, according to our extension rules. const ULONG minExtendPages = MIN_EXTEND_BYTES / dbb->dbb_page_size; const ULONG maxExtendPages = MAX_EXTEND_BYTES / dbb->dbb_page_size; - const ULONG reqPages = pageNum - maxPageNumber + 1; - ULONG extPages; - extPages = MIN(MAX(maxPageNumber / 16, minExtendPages), maxExtendPages); - extPages = MAX(reqPages, extPages); + extPages = MAX(reqPages, MIN(MAX(maxPageNumber / 16, minExtendPages), maxExtendPages)); + } - while (true) + while (true) + { + const ULONG oldMaxPageNumber = maxPageNumber; + try { - const ULONG oldMaxPageNumber = maxPageNumber; - try - { - PIO_extend(tdbb, file, extPages, dbb->dbb_page_size); - break; - } - catch (const status_exception&) + if (!PIO_extend(tdbb, file, extPages, dbb->dbb_page_size)) + return {.success = false}; + + // File was extended, reset cached value + maxPageNumber = 0; + + return {.success = true, .pages_allocated = extPages}; + } + catch (const status_exception&) + { + if (extPages > reqPages && !forceSize) { - if (extPages > reqPages && !forceSize) - { - fb_utils::init_status(tdbb->tdbb_status_vector); + fb_utils::init_status(tdbb->tdbb_status_vector); - // if file was extended, return, else try to extend by less pages + // if file was extended, return, else try to extend by less pages - if (oldMaxPageNumber < maxAlloc()) - return true; + if (const auto newMaxPageNumber = maxAlloc(); oldMaxPageNumber < newMaxPageNumber) + return {.success = true, .pages_allocated = newMaxPageNumber - oldMaxPageNumber}; - extPages = MAX(reqPages, extPages / 2); - } - else - { - gds__log("Error extending file \"%s\" by %lu page(s).\nCurrently allocated %lu pages, requested page number %lu", - file->fil_string, extPages, maxPageNumber, pageNum); - return false; - } + extPages = MAX(reqPages, extPages / 2); + } + else + { + gds__log("Error extending file \"%s\" by %lu page(s).\nCurrently allocated %lu pages, requested page number %lu", + file->fil_string, extPages, maxPageNumber, pageNum); + return {.success = false}; } } - - maxPageNumber = 0; } - return true; } ULONG PageSpace::getSCNPageNum(ULONG sequence) noexcept diff --git a/src/jrd/pag.h b/src/jrd/pag.h index 0683c620f9a..217f01585c8 100644 --- a/src/jrd/pag.h +++ b/src/jrd/pag.h @@ -129,8 +129,13 @@ class PageSpace : public pool_alloc ULONG usedPages(); static ULONG usedPages(const Database* dbb); + struct ExtendResult + { + bool success = false; + ULONG pages_allocated = 0; + }; // extend page space - bool extend(thread_db*, const ULONG, const bool); + ExtendResult extend(thread_db* tdbb, ULONG pageNum, bool forceSize); // get SCN's page number ULONG getSCNPageNum(ULONG sequence) noexcept; From 134f69e43de95e5e76528f00a71109874d93ea5a Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Tue, 30 Sep 2025 10:01:59 +0300 Subject: [PATCH 2/7] refactor(style): Switch to camelCase --- src/jrd/nbak.cpp | 4 ++-- src/jrd/pag.cpp | 60 ++++++++++++++++++++++++------------------------ src/jrd/pag.h | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/jrd/nbak.cpp b/src/jrd/nbak.cpp index 5ff95d6a8df..17df3e2afa3 100644 --- a/src/jrd/nbak.cpp +++ b/src/jrd/nbak.cpp @@ -389,9 +389,9 @@ bool BackupManager::extendDatabase(thread_db* tdbb) if (maxAllocPage >= maxPage) return true; - const auto extension_result = pgSpace->extend(tdbb, maxPage, true); + const auto extensionResult = pgSpace->extend(tdbb, maxPage, true); maxAllocPage = pgSpace->maxAlloc(); - if (extension_result.success && maxAllocPage > maxPage) + if (extensionResult.success && maxAllocPage > maxPage) return true; // Fast file extension not succeeded for some reason, try file extension via direct file writes. diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index ccf7b611174..6dcbdc07b7d 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -266,18 +266,18 @@ namespace CCH_RELEASE(tdbb, &window); } - ULONG ensureDiskSpace(thread_db* tdbb, WIN* pip_window, const PageNumber page_num, const ULONG pip_used) + ULONG ensureDiskSpace(thread_db* tdbb, WIN* pipWindow, const PageNumber pageNum, const ULONG pipUsed) { const auto* dbb = tdbb->getDatabase(); const PageManager& pageMgr = dbb->dbb_page_manager; - PageSpace* const pageSpace = pageMgr.findPageSpace(page_num.getPageSpaceID()); + PageSpace* const pageSpace = pageMgr.findPageSpace(pageNum.getPageSpaceID()); - const ULONG sequence = page_num.getPageNum() / pageMgr.pagesPerPIP; - const ULONG relative_bit = page_num.getPageNum() - sequence * pageMgr.pagesPerPIP; + const ULONG sequence = pageNum.getPageNum() / pageMgr.pagesPerPIP; + const ULONG relativeBit = pageNum.getPageNum() - sequence * pageMgr.pagesPerPIP; - if (relative_bit + 1 <= pip_used) - return pip_used; - fb_assert(relative_bit >= pip_used); + if (relativeBit + 1 <= pipUsed) + return pipUsed; + fb_assert(relativeBit >= pipUsed); BackupManager::StateReadGuard stateGuard(tdbb); @@ -285,50 +285,50 @@ namespace { // Our file is locked, so we can't extend it anyway. // Delta will be extended via simple `write` syscalls. - return relative_bit + 1; + return relativeBit + 1; } - USHORT init_pages = 1; + USHORT initPages = 1; if (!(dbb->dbb_flags & DBB_no_reserve)) { - const unsigned min_extend_pages = MIN_EXTEND_BYTES / dbb->dbb_page_size; + const unsigned minExtendPages = MIN_EXTEND_BYTES / dbb->dbb_page_size; constexpr ULONG MAX_PAGES_IN_EXTENT = 64; - init_pages = sequence ? MAX_PAGES_IN_EXTENT : MIN(pip_used / 16, MAX_PAGES_IN_EXTENT); + initPages = sequence ? MAX_PAGES_IN_EXTENT : MIN(pipUsed / 16, MAX_PAGES_IN_EXTENT); // don't touch pages belongs to the next PIP - init_pages = MIN(init_pages, pageMgr.pagesPerPIP - pip_used); + initPages = MIN(initPages, pageMgr.pagesPerPIP - pipUsed); - if (init_pages < min_extend_pages) - init_pages = 1; + if (initPages < minExtendPages) + initPages = 1; } - if (init_pages < relative_bit + 1 - pip_used) - init_pages = relative_bit + 1 - pip_used; + if (initPages < relativeBit + 1 - pipUsed) + initPages = relativeBit + 1 - pipUsed; // init_pages = FB_ALIGN(init_pages, PAGES_IN_EXTENT); FbLocalStatus status; - const ULONG used_pages = sequence * pageMgr.pagesPerPIP + pip_used; + const ULONG usedPages = sequence * pageMgr.pagesPerPIP + pipUsed; - const bool allocate_exact_number_of_pages = dbb->dbb_flags & DBB_no_reserve; - const auto extension_result = pageSpace->extend(tdbb, used_pages + init_pages, allocate_exact_number_of_pages); - if (extension_result.success) - init_pages = extension_result.pages_allocated; + const bool allocateExactNumberOfPages = dbb->dbb_flags & DBB_no_reserve; + const auto extensionResult = pageSpace->extend(tdbb, usedPages + initPages, allocateExactNumberOfPages); + if (extensionResult.success) + initPages = extensionResult.pagesAllocated; else { // For some reason fast file extension failed, maybe it is not supported by filesystem, or // there is not enough space. Try the old way with writing zeroes to extend file. - init_pages = PIO_init_data(tdbb, pageSpace->file, &status, used_pages, init_pages); + initPages = PIO_init_data(tdbb, pageSpace->file, &status, usedPages, initPages); } - if (!init_pages) + if (!initPages) { // Zero pages was allocated - perhaps it is not supported, // no space left on disk or IO error occurred. Try to write // one page and handle IO errors if any. - WIN window(page_num); + WIN window(pageNum); CCH_fake(tdbb, &window, 1); CCH_must_write(tdbb, &window); try @@ -343,17 +343,17 @@ namespace // normally all page buffers now released by CCH_unwind // only exception is when TDBB_no_cache_unwind flag is set if (tdbb->tdbb_flags & TDBB_no_cache_unwind) - CCH_RELEASE(tdbb, pip_window); + CCH_RELEASE(tdbb, pipWindow); throw; } - return relative_bit + 1; + return relativeBit + 1; } // We can allocate more pages than we requested, so don't return value bigger // than PIP can handle (`pagesPerPIP` is limit) - return MIN(pip_used + init_pages, pageMgr.pagesPerPIP); + return MIN(pipUsed + initPages, pageMgr.pagesPerPIP); } } // namespace @@ -1963,7 +1963,7 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, // First, check it with the cached `maxPageNumber` value. if (pageNum < maxPageNumber || pageNum < maxAlloc()) - return {.success = true, .pages_allocated = maxPageNumber - pageNum}; + return {.success = true, .pagesAllocated = maxPageNumber - pageNum}; const ULONG MAX_EXTEND_BYTES = static_cast(dbb->dbb_config->getDatabaseGrowthIncrement()); if (MAX_EXTEND_BYTES < MIN_EXTEND_BYTES) @@ -1995,7 +1995,7 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, // File was extended, reset cached value maxPageNumber = 0; - return {.success = true, .pages_allocated = extPages}; + return {.success = true, .pagesAllocated = extPages}; } catch (const status_exception&) { @@ -2006,7 +2006,7 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, // if file was extended, return, else try to extend by less pages if (const auto newMaxPageNumber = maxAlloc(); oldMaxPageNumber < newMaxPageNumber) - return {.success = true, .pages_allocated = newMaxPageNumber - oldMaxPageNumber}; + return {.success = true, .pagesAllocated = newMaxPageNumber - oldMaxPageNumber}; extPages = MAX(reqPages, extPages / 2); } diff --git a/src/jrd/pag.h b/src/jrd/pag.h index 217f01585c8..1ac967f01d9 100644 --- a/src/jrd/pag.h +++ b/src/jrd/pag.h @@ -132,7 +132,7 @@ class PageSpace : public pool_alloc struct ExtendResult { bool success = false; - ULONG pages_allocated = 0; + ULONG pagesAllocated = 0; }; // extend page space ExtendResult extend(thread_db* tdbb, ULONG pageNum, bool forceSize); From 09638451b81129dd957f7f6912f1b0d046aa35c0 Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Tue, 30 Sep 2025 17:21:00 +0300 Subject: [PATCH 3/7] Fixes after review --- src/jrd/os/posix/unix.cpp | 4 +--- src/jrd/pag.cpp | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index d62e0556c15..538e7436383 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -302,9 +302,7 @@ bool PIO_expand(const TEXT* file_name, USHORT file_length, TEXT* expanded_name, bool PIO_fast_extension_is_supported(const Jrd::jrd_file& file) noexcept { #if defined(HAVE_LINUX_FALLOC_H) && defined(HAVE_FALLOCATE) - return file.fil_flags & FIL_no_fast_extend - ? false - : true; + return !(file.fil_flags & FIL_no_fast_extend); #else return false; #endif diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index 6dcbdc07b7d..94a10cf6e3b 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -275,9 +275,8 @@ namespace const ULONG sequence = pageNum.getPageNum() / pageMgr.pagesPerPIP; const ULONG relativeBit = pageNum.getPageNum() - sequence * pageMgr.pagesPerPIP; - if (relativeBit + 1 <= pipUsed) + if (relativeBit < pipUsed) return pipUsed; - fb_assert(relativeBit >= pipUsed); BackupManager::StateReadGuard stateGuard(tdbb); From ae4a822746867e19ffef36de260df51a34fc409b Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Tue, 30 Sep 2025 17:22:11 +0300 Subject: [PATCH 4/7] Fix function description according to a new logic --- src/jrd/pag.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index 94a10cf6e3b..a077dff6c29 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -1947,13 +1947,13 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, * extend can't be less than hardcoded value MIN_EXTEND_BYTES and more than * configured value "DatabaseGrowthIncrement" (both values in bytes). * - * If "DatabaseGrowthIncrement" is less than MIN_EXTEND_BYTES then don't - * extend file(s) - * - * If forceSize is true, extend file up to pageNum pages (despite of value + * If `forceSize` is true, extend file up to pageNum pages (despite of value * of "DatabaseGrowthIncrement") and don't make attempts to extend by less * pages. * + * If "DatabaseGrowthIncrement" is less than MIN_EXTEND_BYTES, then treat + * it as if `forceSize` is true. + * **************************************/ fb_assert(dbb == tdbb->getDatabase()); From de152bffb2be8944940ba16e660b5a9d468a2bf8 Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Fri, 3 Oct 2025 14:44:59 +0300 Subject: [PATCH 5/7] Replace `ExtendResult` with `std::optional` --- src/jrd/nbak.cpp | 4 ++-- src/jrd/pag.cpp | 20 ++++++++++---------- src/jrd/pag.h | 7 +------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/jrd/nbak.cpp b/src/jrd/nbak.cpp index 17df3e2afa3..283b668328f 100644 --- a/src/jrd/nbak.cpp +++ b/src/jrd/nbak.cpp @@ -389,9 +389,9 @@ bool BackupManager::extendDatabase(thread_db* tdbb) if (maxAllocPage >= maxPage) return true; - const auto extensionResult = pgSpace->extend(tdbb, maxPage, true); + const auto allocatedPages = pgSpace->extend(tdbb, maxPage, true); maxAllocPage = pgSpace->maxAlloc(); - if (extensionResult.success && maxAllocPage > maxPage) + if (allocatedPages.has_value() && maxAllocPage > maxPage) return true; // Fast file extension not succeeded for some reason, try file extension via direct file writes. diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index a077dff6c29..ac71232234d 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -312,9 +312,9 @@ namespace const ULONG usedPages = sequence * pageMgr.pagesPerPIP + pipUsed; const bool allocateExactNumberOfPages = dbb->dbb_flags & DBB_no_reserve; - const auto extensionResult = pageSpace->extend(tdbb, usedPages + initPages, allocateExactNumberOfPages); - if (extensionResult.success) - initPages = extensionResult.pagesAllocated; + const auto allocatedPages = pageSpace->extend(tdbb, usedPages + initPages, allocateExactNumberOfPages); + if (allocatedPages.has_value()) + initPages = allocatedPages.value(); else { // For some reason fast file extension failed, maybe it is not supported by filesystem, or @@ -1938,7 +1938,7 @@ ULONG PageSpace::usedPages(const Database* dbb) return pgSpace->usedPages(); } -PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) +std::optional PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) { /************************************** * @@ -1958,11 +1958,11 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, fb_assert(dbb == tdbb->getDatabase()); if (!PIO_fast_extension_is_supported(*file)) - return {.success = false}; + return std::nullopt; // First, check it with the cached `maxPageNumber` value. if (pageNum < maxPageNumber || pageNum < maxAlloc()) - return {.success = true, .pagesAllocated = maxPageNumber - pageNum}; + return maxPageNumber - pageNum; const ULONG MAX_EXTEND_BYTES = static_cast(dbb->dbb_config->getDatabaseGrowthIncrement()); if (MAX_EXTEND_BYTES < MIN_EXTEND_BYTES) @@ -1989,12 +1989,12 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, try { if (!PIO_extend(tdbb, file, extPages, dbb->dbb_page_size)) - return {.success = false}; + return std::nullopt; // File was extended, reset cached value maxPageNumber = 0; - return {.success = true, .pagesAllocated = extPages}; + return extPages; } catch (const status_exception&) { @@ -2005,7 +2005,7 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, // if file was extended, return, else try to extend by less pages if (const auto newMaxPageNumber = maxAlloc(); oldMaxPageNumber < newMaxPageNumber) - return {.success = true, .pagesAllocated = newMaxPageNumber - oldMaxPageNumber}; + return newMaxPageNumber - oldMaxPageNumber; extPages = MAX(reqPages, extPages / 2); } @@ -2013,7 +2013,7 @@ PageSpace::ExtendResult PageSpace::extend(thread_db* tdbb, const ULONG pageNum, { gds__log("Error extending file \"%s\" by %lu page(s).\nCurrently allocated %lu pages, requested page number %lu", file->fil_string, extPages, maxPageNumber, pageNum); - return {.success = false}; + return std::nullopt; } } } diff --git a/src/jrd/pag.h b/src/jrd/pag.h index 1ac967f01d9..467d1d22251 100644 --- a/src/jrd/pag.h +++ b/src/jrd/pag.h @@ -129,13 +129,8 @@ class PageSpace : public pool_alloc ULONG usedPages(); static ULONG usedPages(const Database* dbb); - struct ExtendResult - { - bool success = false; - ULONG pagesAllocated = 0; - }; // extend page space - ExtendResult extend(thread_db* tdbb, ULONG pageNum, bool forceSize); + std::optional extend(thread_db* tdbb, ULONG pageNum, bool forceSize); // get SCN's page number ULONG getSCNPageNum(ULONG sequence) noexcept; From 2db7f5381412cc16ab4b6b8ca7d6b0da80147252 Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Mon, 6 Oct 2025 15:50:44 +0300 Subject: [PATCH 6/7] Fixes after review --- src/jrd/nbak.cpp | 10 ++++++---- src/jrd/pag.cpp | 16 +++++++--------- src/jrd/pag.h | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/jrd/nbak.cpp b/src/jrd/nbak.cpp index 283b668328f..be471beb9dd 100644 --- a/src/jrd/nbak.cpp +++ b/src/jrd/nbak.cpp @@ -389,10 +389,12 @@ bool BackupManager::extendDatabase(thread_db* tdbb) if (maxAllocPage >= maxPage) return true; - const auto allocatedPages = pgSpace->extend(tdbb, maxPage, true); - maxAllocPage = pgSpace->maxAlloc(); - if (allocatedPages.has_value() && maxAllocPage > maxPage) - return true; + if (pgSpace->extend(tdbb, maxPage, true)) + { + maxAllocPage = pgSpace->maxAlloc(); + if (maxAllocPage >= maxPage) + return true; + } // Fast file extension not succeeded for some reason, try file extension via direct file writes. while (maxAllocPage < maxPage) diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index ac71232234d..57742450c3c 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -312,9 +312,8 @@ namespace const ULONG usedPages = sequence * pageMgr.pagesPerPIP + pipUsed; const bool allocateExactNumberOfPages = dbb->dbb_flags & DBB_no_reserve; - const auto allocatedPages = pageSpace->extend(tdbb, usedPages + initPages, allocateExactNumberOfPages); - if (allocatedPages.has_value()) - initPages = allocatedPages.value(); + if (const auto allocatedPages = pageSpace->extend(tdbb, usedPages + initPages, allocateExactNumberOfPages)) + initPages = allocatedPages; else { // For some reason fast file extension failed, maybe it is not supported by filesystem, or @@ -1938,7 +1937,7 @@ ULONG PageSpace::usedPages(const Database* dbb) return pgSpace->usedPages(); } -std::optional PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) +ULONG PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) { /************************************** * @@ -1958,7 +1957,7 @@ std::optional PageSpace::extend(thread_db* tdbb, const ULONG pageNum, boo fb_assert(dbb == tdbb->getDatabase()); if (!PIO_fast_extension_is_supported(*file)) - return std::nullopt; + return 0; // First, check it with the cached `maxPageNumber` value. if (pageNum < maxPageNumber || pageNum < maxAlloc()) @@ -1989,10 +1988,9 @@ std::optional PageSpace::extend(thread_db* tdbb, const ULONG pageNum, boo try { if (!PIO_extend(tdbb, file, extPages, dbb->dbb_page_size)) - return std::nullopt; + return 0; - // File was extended, reset cached value - maxPageNumber = 0; + maxPageNumber += extPages; return extPages; } @@ -2013,7 +2011,7 @@ std::optional PageSpace::extend(thread_db* tdbb, const ULONG pageNum, boo { gds__log("Error extending file \"%s\" by %lu page(s).\nCurrently allocated %lu pages, requested page number %lu", file->fil_string, extPages, maxPageNumber, pageNum); - return std::nullopt; + return 0; } } } diff --git a/src/jrd/pag.h b/src/jrd/pag.h index 467d1d22251..db3753e538a 100644 --- a/src/jrd/pag.h +++ b/src/jrd/pag.h @@ -130,7 +130,7 @@ class PageSpace : public pool_alloc static ULONG usedPages(const Database* dbb); // extend page space - std::optional extend(thread_db* tdbb, ULONG pageNum, bool forceSize); + ULONG extend(thread_db* tdbb, ULONG pageNum, bool forceSize); // get SCN's page number ULONG getSCNPageNum(ULONG sequence) noexcept; From 65796848797d27f7df009f186b4e0c1cb3da6506 Mon Sep 17 00:00:00 2001 From: Artyom Ivanov Date: Tue, 7 Oct 2025 11:53:40 +0300 Subject: [PATCH 7/7] Reset cached value --- src/jrd/pag.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jrd/pag.cpp b/src/jrd/pag.cpp index 57742450c3c..f5b0ee8e2de 100644 --- a/src/jrd/pag.cpp +++ b/src/jrd/pag.cpp @@ -1990,7 +1990,8 @@ ULONG PageSpace::extend(thread_db* tdbb, const ULONG pageNum, bool forceSize) if (!PIO_extend(tdbb, file, extPages, dbb->dbb_page_size)) return 0; - maxPageNumber += extPages; + // File was extended, reset the cached value + maxPageNumber = 0; return extPages; }