diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 4ae37f988..c7f10e158 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -56,6 +56,7 @@ src_libbitcoin_database_la_SOURCES = \ ${srcdir}/../../src/locks/flush_lock.cpp \ ${srcdir}/../../src/locks/interprocess_lock.cpp \ ${srcdir}/../../src/memory/mman.cpp \ + ${srcdir}/../../src/memory/mstage.cpp \ ${srcdir}/../../src/memory/utilities.cpp \ ${srcdir}/../../src/types/history.cpp \ ${srcdir}/../../src/types/multisig_view.cpp \ @@ -94,6 +95,7 @@ include_bitcoin_database_impl_memory_HEADERS = \ ${srcdir}/../../include/bitcoin/database/impl/memory/mmap.ipp \ ${srcdir}/../../include/bitcoin/database/impl/memory/mmap_dispatch.ipp \ ${srcdir}/../../include/bitcoin/database/impl/memory/mmap_private.ipp \ + ${srcdir}/../../include/bitcoin/database/impl/memory/mmap_staging.ipp \ ${srcdir}/../../include/bitcoin/database/impl/memory/mmap_storage.ipp include_bitcoin_database_impl_primitivesdir = \ @@ -223,6 +225,7 @@ include_bitcoin_database_memory_HEADERS = \ ${srcdir}/../../include/bitcoin/database/memory/mman.hpp \ ${srcdir}/../../include/bitcoin/database/memory/mmap.hpp \ ${srcdir}/../../include/bitcoin/database/memory/mmaps.hpp \ + ${srcdir}/../../include/bitcoin/database/memory/mstage.hpp \ ${srcdir}/../../include/bitcoin/database/memory/reader.hpp \ ${srcdir}/../../include/bitcoin/database/memory/streamers.hpp \ ${srcdir}/../../include/bitcoin/database/memory/utilities.hpp diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index f6378c742..19300d399 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -1,332 +1,335 @@ - - - - - v143 - {62D7FBEE-4D52-424A-8938-59756E13D9F5} - libbitcoin-database - - - - DebugDLL - ARM - - - ReleaseDLL - ARM - - - DebugLTCG - ARM - - - ReleaseLTCG - ARM - - - DebugLIB - ARM - - - ReleaseLIB - ARM - - - DebugDLL - ARM64 - - - ReleaseDLL - ARM64 - - - DebugLTCG - ARM64 - - - ReleaseLTCG - ARM64 - - - DebugLIB - ARM64 - - - ReleaseLIB - ARM64 - - - DebugDLL - Win32 - - - ReleaseDLL - Win32 - - - DebugLTCG - Win32 - - - ReleaseLTCG - Win32 - - - DebugLIB - Win32 - - - ReleaseLIB - Win32 - - - DebugDLL - x64 - - - ReleaseDLL - x64 - - - DebugLTCG - x64 - - - ReleaseLTCG - x64 - - - DebugLIB - x64 - - - ReleaseLIB - x64 - - - - StaticLibrary - DynamicLibrary - - - - - - - - - - - - - - $(IntDir)src_file_utilities.obj - - - - - - - $(IntDir)src_memory_utilities.obj - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}. - - - - - - - - - - - - - - - - + + + + + v143 + {62D7FBEE-4D52-424A-8938-59756E13D9F5} + libbitcoin-database + + + + DebugDLL + ARM + + + ReleaseDLL + ARM + + + DebugLTCG + ARM + + + ReleaseLTCG + ARM + + + DebugLIB + ARM + + + ReleaseLIB + ARM + + + DebugDLL + ARM64 + + + ReleaseDLL + ARM64 + + + DebugLTCG + ARM64 + + + ReleaseLTCG + ARM64 + + + DebugLIB + ARM64 + + + ReleaseLIB + ARM64 + + + DebugDLL + Win32 + + + ReleaseDLL + Win32 + + + DebugLTCG + Win32 + + + ReleaseLTCG + Win32 + + + DebugLIB + Win32 + + + ReleaseLIB + Win32 + + + DebugDLL + x64 + + + ReleaseDLL + x64 + + + DebugLTCG + x64 + + + ReleaseLTCG + x64 + + + DebugLIB + x64 + + + ReleaseLIB + x64 + + + + StaticLibrary + DynamicLibrary + + + + + + + + + + + + + + $(IntDir)src_file_utilities.obj + + + + + + + + $(IntDir)src_memory_utilities.obj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}. + + + + + + + + + + + + + + + + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index caaa85efe..85293bd77 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -1,600 +1,609 @@ - - - - - - {62D7FBEE-4D52-424A-0000-000000000000} - - - {62D7FBEE-4D52-424A-0000-000000000001} - - - {62D7FBEE-4D52-424A-0000-000000000002} - - - {62D7FBEE-4D52-424A-0000-000000000003} - - - {62D7FBEE-4D52-424A-0000-000000000004} - - - {62D7FBEE-4D52-424A-0000-000000000005} - - - {62D7FBEE-4D52-424A-0000-000000000006} - - - {62D7FBEE-4D52-424A-0000-000000000007} - - - {62D7FBEE-4D52-424A-0000-000000000008} - - - {62D7FBEE-4D52-424A-0000-000000000009} - - - {62D7FBEE-4D52-424A-0000-00000000000A} - - - {62D7FBEE-4D52-424A-0000-00000000000B} - - - {62D7FBEE-4D52-424A-0000-00000000000C} - - - {62D7FBEE-4D52-424A-0000-00000000000D} - - - {62D7FBEE-4D52-424A-0000-00000000000E} - - - {62D7FBEE-4D52-424A-0000-00000000000F} - - - {62D7FBEE-4D52-424A-0000-000000000001} - - - {62D7FBEE-4D52-424A-0000-000000000002} - - - {62D7FBEE-4D52-424A-0000-000000000003} - - - {62D7FBEE-4D52-424A-0000-000000000004} - - - {62D7FBEE-4D52-424A-0000-000000000005} - - - {62D7FBEE-4D52-424A-0000-000000000006} - - - {62D7FBEE-4D52-424A-0000-000000000007} - - - {62D7FBEE-4D52-424A-0000-000000000008} - - - {62D7FBEE-4D52-424A-0000-000000000009} - - - {62D7FBEE-4D52-424A-0000-000000000010} - - - {62D7FBEE-4D52-424A-0000-0000000000A1} - - - {62D7FBEE-4D52-424A-0000-0000000000B1} - - - {62D7FBEE-4D52-424A-0000-0000000000C1} - - - - - src - - - src - - - src\file - - - src\file - - - src\locks - - - src\locks - - - src\locks - - - src\memory - - - src\memory - - - src - - - src\types - - - src\types - - - src\types - - - - - include\bitcoin - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database\file - - - include\bitcoin\database\file - - - include\bitcoin\database\file - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory\interfaces - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables\indexes - - - include\bitcoin\database\tables\indexes - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database - - - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - - - - - + + + + + + {62D7FBEE-4D52-424A-0000-000000000000} + + + {62D7FBEE-4D52-424A-0000-000000000001} + + + {62D7FBEE-4D52-424A-0000-000000000002} + + + {62D7FBEE-4D52-424A-0000-000000000003} + + + {62D7FBEE-4D52-424A-0000-000000000004} + + + {62D7FBEE-4D52-424A-0000-000000000005} + + + {62D7FBEE-4D52-424A-0000-000000000006} + + + {62D7FBEE-4D52-424A-0000-000000000007} + + + {62D7FBEE-4D52-424A-0000-000000000008} + + + {62D7FBEE-4D52-424A-0000-000000000009} + + + {62D7FBEE-4D52-424A-0000-00000000000A} + + + {62D7FBEE-4D52-424A-0000-00000000000B} + + + {62D7FBEE-4D52-424A-0000-00000000000C} + + + {62D7FBEE-4D52-424A-0000-00000000000D} + + + {62D7FBEE-4D52-424A-0000-00000000000E} + + + {62D7FBEE-4D52-424A-0000-00000000000F} + + + {62D7FBEE-4D52-424A-0000-000000000001} + + + {62D7FBEE-4D52-424A-0000-000000000002} + + + {62D7FBEE-4D52-424A-0000-000000000003} + + + {62D7FBEE-4D52-424A-0000-000000000004} + + + {62D7FBEE-4D52-424A-0000-000000000005} + + + {62D7FBEE-4D52-424A-0000-000000000006} + + + {62D7FBEE-4D52-424A-0000-000000000007} + + + {62D7FBEE-4D52-424A-0000-000000000008} + + + {62D7FBEE-4D52-424A-0000-000000000009} + + + {62D7FBEE-4D52-424A-0000-000000000010} + + + {62D7FBEE-4D52-424A-0000-0000000000A1} + + + {62D7FBEE-4D52-424A-0000-0000000000B1} + + + {62D7FBEE-4D52-424A-0000-0000000000C1} + + + + + src + + + src + + + src\file + + + src\file + + + src\locks + + + src\locks + + + src\locks + + + src\memory + + + src\memory + + + src\memory + + + src + + + src\types + + + src\types + + + src\types + + + + + include\bitcoin + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database\file + + + include\bitcoin\database\file + + + include\bitcoin\database\file + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory\interfaces + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables\indexes + + + include\bitcoin\database\tables\indexes + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database + + + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + + + + + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index a44a1e8e6..49769e11e 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -1,332 +1,335 @@ - - - - - v145 - {62D7FBEE-4D52-424A-8938-59756E13D9F5} - libbitcoin-database - - - - DebugDLL - ARM - - - ReleaseDLL - ARM - - - DebugLTCG - ARM - - - ReleaseLTCG - ARM - - - DebugLIB - ARM - - - ReleaseLIB - ARM - - - DebugDLL - ARM64 - - - ReleaseDLL - ARM64 - - - DebugLTCG - ARM64 - - - ReleaseLTCG - ARM64 - - - DebugLIB - ARM64 - - - ReleaseLIB - ARM64 - - - DebugDLL - Win32 - - - ReleaseDLL - Win32 - - - DebugLTCG - Win32 - - - ReleaseLTCG - Win32 - - - DebugLIB - Win32 - - - ReleaseLIB - Win32 - - - DebugDLL - x64 - - - ReleaseDLL - x64 - - - DebugLTCG - x64 - - - ReleaseLTCG - x64 - - - DebugLIB - x64 - - - ReleaseLIB - x64 - - - - StaticLibrary - DynamicLibrary - - - - - - - - - - - - - - $(IntDir)src_file_utilities.obj - - - - - - - $(IntDir)src_memory_utilities.obj - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}. - - - - - - - - - - - - - - - - + + + + + v145 + {62D7FBEE-4D52-424A-8938-59756E13D9F5} + libbitcoin-database + + + + DebugDLL + ARM + + + ReleaseDLL + ARM + + + DebugLTCG + ARM + + + ReleaseLTCG + ARM + + + DebugLIB + ARM + + + ReleaseLIB + ARM + + + DebugDLL + ARM64 + + + ReleaseDLL + ARM64 + + + DebugLTCG + ARM64 + + + ReleaseLTCG + ARM64 + + + DebugLIB + ARM64 + + + ReleaseLIB + ARM64 + + + DebugDLL + Win32 + + + ReleaseDLL + Win32 + + + DebugLTCG + Win32 + + + ReleaseLTCG + Win32 + + + DebugLIB + Win32 + + + ReleaseLIB + Win32 + + + DebugDLL + x64 + + + ReleaseDLL + x64 + + + DebugLTCG + x64 + + + ReleaseLTCG + x64 + + + DebugLIB + x64 + + + ReleaseLIB + x64 + + + + StaticLibrary + DynamicLibrary + + + + + + + + + + + + + + $(IntDir)src_file_utilities.obj + + + + + + + + $(IntDir)src_memory_utilities.obj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The Missing file is {0}. + + + + + + + + + + + + + + + + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index caaa85efe..85293bd77 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -1,600 +1,609 @@ - - - - - - {62D7FBEE-4D52-424A-0000-000000000000} - - - {62D7FBEE-4D52-424A-0000-000000000001} - - - {62D7FBEE-4D52-424A-0000-000000000002} - - - {62D7FBEE-4D52-424A-0000-000000000003} - - - {62D7FBEE-4D52-424A-0000-000000000004} - - - {62D7FBEE-4D52-424A-0000-000000000005} - - - {62D7FBEE-4D52-424A-0000-000000000006} - - - {62D7FBEE-4D52-424A-0000-000000000007} - - - {62D7FBEE-4D52-424A-0000-000000000008} - - - {62D7FBEE-4D52-424A-0000-000000000009} - - - {62D7FBEE-4D52-424A-0000-00000000000A} - - - {62D7FBEE-4D52-424A-0000-00000000000B} - - - {62D7FBEE-4D52-424A-0000-00000000000C} - - - {62D7FBEE-4D52-424A-0000-00000000000D} - - - {62D7FBEE-4D52-424A-0000-00000000000E} - - - {62D7FBEE-4D52-424A-0000-00000000000F} - - - {62D7FBEE-4D52-424A-0000-000000000001} - - - {62D7FBEE-4D52-424A-0000-000000000002} - - - {62D7FBEE-4D52-424A-0000-000000000003} - - - {62D7FBEE-4D52-424A-0000-000000000004} - - - {62D7FBEE-4D52-424A-0000-000000000005} - - - {62D7FBEE-4D52-424A-0000-000000000006} - - - {62D7FBEE-4D52-424A-0000-000000000007} - - - {62D7FBEE-4D52-424A-0000-000000000008} - - - {62D7FBEE-4D52-424A-0000-000000000009} - - - {62D7FBEE-4D52-424A-0000-000000000010} - - - {62D7FBEE-4D52-424A-0000-0000000000A1} - - - {62D7FBEE-4D52-424A-0000-0000000000B1} - - - {62D7FBEE-4D52-424A-0000-0000000000C1} - - - - - src - - - src - - - src\file - - - src\file - - - src\locks - - - src\locks - - - src\locks - - - src\memory - - - src\memory - - - src - - - src\types - - - src\types - - - src\types - - - - - include\bitcoin - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database\file - - - include\bitcoin\database\file - - - include\bitcoin\database\file - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\locks - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory\interfaces - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\memory - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database\primitives - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\archives - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables\caches - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables\indexes - - - include\bitcoin\database\tables\indexes - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables\optionals - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\tables - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database\types - - - include\bitcoin\database - - - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\memory - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\primitives - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query\address - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\archive - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query\batch - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query\consensus - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query\navigate - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\query - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - include\bitcoin\database\impl\store - - - - - - - + + + + + + {62D7FBEE-4D52-424A-0000-000000000000} + + + {62D7FBEE-4D52-424A-0000-000000000001} + + + {62D7FBEE-4D52-424A-0000-000000000002} + + + {62D7FBEE-4D52-424A-0000-000000000003} + + + {62D7FBEE-4D52-424A-0000-000000000004} + + + {62D7FBEE-4D52-424A-0000-000000000005} + + + {62D7FBEE-4D52-424A-0000-000000000006} + + + {62D7FBEE-4D52-424A-0000-000000000007} + + + {62D7FBEE-4D52-424A-0000-000000000008} + + + {62D7FBEE-4D52-424A-0000-000000000009} + + + {62D7FBEE-4D52-424A-0000-00000000000A} + + + {62D7FBEE-4D52-424A-0000-00000000000B} + + + {62D7FBEE-4D52-424A-0000-00000000000C} + + + {62D7FBEE-4D52-424A-0000-00000000000D} + + + {62D7FBEE-4D52-424A-0000-00000000000E} + + + {62D7FBEE-4D52-424A-0000-00000000000F} + + + {62D7FBEE-4D52-424A-0000-000000000001} + + + {62D7FBEE-4D52-424A-0000-000000000002} + + + {62D7FBEE-4D52-424A-0000-000000000003} + + + {62D7FBEE-4D52-424A-0000-000000000004} + + + {62D7FBEE-4D52-424A-0000-000000000005} + + + {62D7FBEE-4D52-424A-0000-000000000006} + + + {62D7FBEE-4D52-424A-0000-000000000007} + + + {62D7FBEE-4D52-424A-0000-000000000008} + + + {62D7FBEE-4D52-424A-0000-000000000009} + + + {62D7FBEE-4D52-424A-0000-000000000010} + + + {62D7FBEE-4D52-424A-0000-0000000000A1} + + + {62D7FBEE-4D52-424A-0000-0000000000B1} + + + {62D7FBEE-4D52-424A-0000-0000000000C1} + + + + + src + + + src + + + src\file + + + src\file + + + src\locks + + + src\locks + + + src\locks + + + src\memory + + + src\memory + + + src\memory + + + src + + + src\types + + + src\types + + + src\types + + + + + include\bitcoin + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database\file + + + include\bitcoin\database\file + + + include\bitcoin\database\file + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\locks + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory\interfaces + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\memory + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database\primitives + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\archives + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables\caches + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables\indexes + + + include\bitcoin\database\tables\indexes + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables\optionals + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\tables + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database\types + + + include\bitcoin\database + + + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\memory + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\primitives + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query\address + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\archive + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query\batch + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query\consensus + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query\navigate + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\query + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + include\bitcoin\database\impl\store + + + + + + + diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index 8770fb51c..485a1cd0f 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/impl/memory/mmap.ipp b/include/bitcoin/database/impl/memory/mmap.ipp index 18071a865..b2cbc8ade 100644 --- a/include/bitcoin/database/impl/memory/mmap.ipp +++ b/include/bitcoin/database/impl/memory/mmap.ipp @@ -1,118 +1,134 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP -#define LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP - -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace database { - -// Constructors. -// ---------------------------------------------------------------------------- - -TEMPLATE -CLASS::mmap(const path& filename, size_t minimum, size_t expansion, - bool random) NOEXCEPT - requires (is_one(columns)) - : filenames_{ filename }, - minimum_(to_rows(minimum)), - expansion_(expansion), - random_(random), - opened_{ file::invalid } -{ -} - -TEMPLATE -CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion, - bool random) NOEXCEPT - requires (columns > one) - : filenames_(filenames), - minimum_(to_rows(minimum)), - expansion_(expansion), - random_(random), - opened_{} -{ - opened_.fill(file::invalid); -} - -TEMPLATE -CLASS::~mmap() NOEXCEPT -{ - BC_ASSERT(!loaded_.load()); - BC_ASSERT(is_zero(logical_.load())); - BC_ASSERT(is_zero(capacity_.load())); - BC_ASSERT(std::ranges::all_of(memory_map_, - [](auto map) NOEXCEPT { return is_null(map); })); - BC_ASSERT(std::ranges::all_of(opened_, - [](auto opened) NOEXCEPT { return opened == file::invalid; })); -} - -TEMPLATE -bool CLASS::is_open() const NOEXCEPT -{ - std::shared_lock field_lock(field_mutex_); - return opened_.front() != file::invalid; -} - -TEMPLATE -bool CLASS::is_loaded() const NOEXCEPT -{ - return loaded_.load(); -} - -// protected -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::to_capacity(size_t required) const NOEXCEPT -{ - // Covert required rows to capacity-padded rows. - using namespace system; - const auto growth = ceilinged_multiply(required, expansion_) / 100u; - return std::max(minimum_, ceilinged_add(required, growth)); -} - -// Write-write protected by remap_mutex. -TEMPLATE -void CLASS::set_first_code(const error::error_t& ec) NOEXCEPT -{ - if (!fault_.load()) - { - fault_.store(true); - - // error is atomic for public read exposure. - error_.store(ec); - } -} - -TEMPLATE -void CLASS::set_disk_space(size_t required) NOEXCEPT -{ - space_.store(required); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP +#define LIBBITCOIN_DATABASE_MEMORY_MMAP_IPP + +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace database { + +// Constructors. +// ---------------------------------------------------------------------------- + +TEMPLATE +CLASS::mmap(const path& filename, size_t minimum, size_t expansion, + bool random, bool staged) NOEXCEPT + requires (is_one(columns)) + : filenames_{ filename }, + minimum_(to_rows(minimum)), + expansion_(expansion), + random_(random), + staged_(staged), + opened_{ file::invalid } +{ +} + +TEMPLATE +CLASS::mmap(const paths& filenames, size_t minimum, size_t expansion, + bool random, bool staged) NOEXCEPT + requires (columns > one) + : filenames_(filenames), + minimum_(to_rows(minimum)), + expansion_(expansion), + random_(random), + staged_(staged), + opened_{} +{ + opened_.fill(file::invalid); +} + +TEMPLATE +CLASS::~mmap() NOEXCEPT +{ +#if defined(MANAGE_STAGING) + // Join a settler left running by an unload bypass (thread safety). + settler_stop_(); +#endif + + BC_ASSERT(!loaded_.load()); + BC_ASSERT(is_zero(logical_.load())); + BC_ASSERT(is_zero(capacity_.load())); + BC_ASSERT(std::ranges::all_of(memory_map_, + [](auto map) NOEXCEPT { return is_null(map); })); + BC_ASSERT(std::ranges::all_of(opened_, + [](auto opened) NOEXCEPT { return opened == file::invalid; })); +#if defined(MANAGE_STAGING) + BC_ASSERT(std::ranges::all_of(reserved_, + [](auto reserved) NOEXCEPT { return is_zero(reserved); })); +#endif +} + +TEMPLATE +bool CLASS::is_open() const NOEXCEPT +{ + std::shared_lock field_lock(field_mutex_); + return opened_.front() != file::invalid; +} + +TEMPLATE +bool CLASS::is_loaded() const NOEXCEPT +{ + return loaded_.load(); +} + +// protected +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::to_capacity(size_t required) const NOEXCEPT +{ + // Covert required rows to capacity-padded rows. + using namespace system; + const auto growth = ceilinged_multiply(required, expansion_) / 100u; + return std::max(minimum_, ceilinged_add(required, growth)); +} + +// Write-write protected by remap_mutex. +TEMPLATE +void CLASS::set_first_code(const error::error_t& ec) NOEXCEPT +{ + if (!fault_.load()) + { + fault_.store(true); + + // error is atomic for public read exposure. + error_.store(ec); + +#if defined(MANAGE_STAGING) + // A fault may stop draining, so it must release throttled callers. + signal_(); +#endif + } +} + +TEMPLATE +void CLASS::set_disk_space(size_t required) NOEXCEPT +{ + space_.store(required); +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/memory/mmap_private.ipp b/include/bitcoin/database/impl/memory/mmap_private.ipp index b32266312..e89c8968a 100644 --- a/include/bitcoin/database/impl/memory/mmap_private.ipp +++ b/include/bitcoin/database/impl/memory/mmap_private.ipp @@ -24,6 +24,8 @@ #include #include #include +#include +#include namespace libbitcoin { namespace database { @@ -34,15 +36,33 @@ namespace database { TEMPLATE template -bool CLASS::flush_all_(std::index_sequence) NOEXCEPT +bool CLASS::flush_all_(size_t rows, std::index_sequence) NOEXCEPT { - return (flush_() && ...); + return (flush_(rows) && ...); } TEMPLATE template bool CLASS::map_all_(std::index_sequence) NOEXCEPT { +#if defined(MANAGE_STAGING) + using namespace system; + const auto page = page_size(); + + // Page size must be a power of two. + if (is_zero(page) || !is_one(ones_count(page))) + { + set_first_code(error::sysconf_failure); + capacity_.store(zero); + return false; + } + + page_ = page; + window_.store(zero); + settled_.store(staged_ ? logical_.load() : zero); + frontier_.store(staged_ ? logical_.load() : zero); +#endif + if (!(map_() && ...)) { capacity_.store(zero); @@ -60,6 +80,13 @@ bool CLASS::unmap_all_(std::index_sequence) NOEXCEPT const auto capacity = capacity_.load(); const auto success = (unmap_(capacity) && ...); capacity_.store(zero); + +#if defined(MANAGE_STAGING) + window_.store(zero); + settled_.store(zero); + frontier_.store(zero); +#endif + return success; } @@ -84,20 +111,35 @@ bool CLASS::remap_all_(size_t capacity, std::index_sequence) NOEXCEPT // Never results in unmapped. TEMPLATE template -bool CLASS::flush_() NOEXCEPT +bool CLASS::flush_(size_t + #if defined(MANAGE_STAGING) || defined(HAVE_MSC) + rows + #endif +) NOEXCEPT { -#if defined(HAVE_MSC) +#if defined(MANAGE_STAGING) + // Transfer unflushed rows from anonymous memory to the file. Settled rows + // are already on disk (staged); unstaged content is written in full. + const auto from = to_width(staged_ ? settled_.load() : zero); + const auto to = to_width(rows); + + const auto success = + ((from >= to) || pwrite_all(opened_[Column], + std::next(memory_map_[Column], from), to - from, from)) +#if defined(F_FULLFSYNC) + // non-standard macOS behavior: news.ycombinator.com/item?id=30372218 + && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail); +#else + && (::fsync(opened_[Column]) != fail); +#endif +#elif defined(HAVE_MSC) // unmap (and therefore msync) must be called before ftruncate. // "To flush all the dirty pages plus the metadata for the file and ensure // that they are physically written to disk..." - const auto size = to_width(logical_.load()); + const auto size = to_width(rows); const auto success = (::msync(memory_map_[Column], size, MS_SYNC) != fail) && (::fsync(opened_[Column]) != fail); -#elif defined(F_FULLFSYNC) - // macOS msync fails with zero logical size (but we are no longer calling). - // non-standard macOS behavior: news.ycombinator.com/item?id=30372218 - const auto success = ::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail; #else // msync should not be required on modern linux, see linus et al. // stackoverflow.com/questions/5902629/mmap-msync-and-linux-process-termination @@ -137,11 +179,34 @@ bool CLASS::release_(size_t size) NOEXCEPT // Always results in unmapped, trims to logical (can be zero). TEMPLATE template -bool CLASS::unmap_(size_t size) NOEXCEPT +bool CLASS::unmap_(size_t + #if !defined(MANAGE_STAGING) + size + #endif +) NOEXCEPT { const auto logical = to_width(logical_.load()); -#if defined(HAVE_MSC) +#if defined(MANAGE_STAGING) + // Persist unflushed rows, trim preallocation to logical, sync to disk. + const auto from = to_width(staged_ ? settled_.load() : zero); + const auto transferred = + ((from >= logical) || pwrite_all(opened_[Column], + std::next(memory_map_[Column], from), logical - from, from)) + && (::ftruncate(opened_[Column], logical) != fail) +#if defined(F_FULLFSYNC) + && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail); +#else + && (::fsync(opened_[Column]) != fail); +#endif + + // Order ensures release of the reservation in case of transfer failure. + const auto success = (::munmap(memory_map_[Column], + reserved_[Column]) != fail) && transferred; + + memory_map_[Column] = {}; + reserved_[Column] = zero; +#elif defined(HAVE_MSC) // Windows cannot resize a mapped file. // msync requires the live mapping, ftruncate requires it gone. const auto synced = @@ -155,11 +220,7 @@ bool CLASS::unmap_(size_t size) NOEXCEPT // POSIX permits resizing a mapped file. const auto truncated = (::ftruncate(opened_[Column], logical) != fail) -#if defined(F_FULLFSYNC) - && (::fcntl(opened_[Column], F_FULLFSYNC, 0) != fail); -#else && (::fsync(opened_[Column]) != fail); -#endif // Order ensures release in case of truncate failure. const auto success = release_(size) && truncated; @@ -179,6 +240,9 @@ TEMPLATE template bool CLASS::map_() NOEXCEPT { +#if defined(MANAGE_STAGING) + return stage_(); +#else auto size = logical_.load(); // Cannot map empty file, and want minimum capacity, so expand as required. @@ -191,6 +255,7 @@ bool CLASS::map_() NOEXCEPT MAP_SHARED, opened_[Column], 0)); return finalize_(size); +#endif } // Remap failure results in unmapped. @@ -205,49 +270,32 @@ bool CLASS::remap_(size_t size) NOEXCEPT if (is_zero(size)) size = minimum_; -#if !defined(HAVE_MSC) && !defined(MREMAP_MAYMOVE) - // macOS cannot remap in place, so release the mapping without trimming and - // the file remains at capacity_ bytes and resize_'s fallocate delta (and - // the fallocate shim's preallocation window) are exact by construction. - if (!release_(capacity_.load())) - return false; - +#if defined(MANAGE_STAGING) + // The file is preallocated to capacity, preserving disk full detection at + // allocation, and growth commits reserved anonymous pages in place, so no + // mapping is released and the map base is stable within the reservation. if (!resize_(size)) - { - map_(); return false; - } + + return commit_(size); #else if (!resize_(size)) return false; -#endif #if defined(HAVE_MSC) - // mman-win32 mremap hack (umap/map) requires flags and file descriptor. memory_map_[Column] = system::pointer_cast( ::mremap_(memory_map_[Column], to_width(capacity_.load()), to_width(size), PROT_READ | PROT_WRITE, MAP_SHARED, opened_[Column])); - -#elif defined(MREMAP_MAYMOVE) - +#else memory_map_[Column] = system::pointer_cast( ::mremap(memory_map_[Column], to_width(capacity_.load()), to_width(size), MREMAP_MAYMOVE)); - -#else - - // macOS does not define mremap or MREMAP_MAYMOVE. The prior mapping was - // released above and the resized file is mapped fresh. - // TODO: see "MREMAP_MAYMOVE" in sqlite for map extension technique. - memory_map_[Column] = system::pointer_cast( - ::mmap(nullptr, to_width(size), PROT_READ | PROT_WRITE, - MAP_SHARED, opened_[Column], 0)); - #endif return finalize_(size); +#endif // MANAGE_STAGING } // disk_full: space is set but no code is set with false return. @@ -342,6 +390,7 @@ bool CLASS::finalize_(size_t return true; } + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/memory/mmap_staging.ipp b/include/bitcoin/database/impl/memory/mmap_staging.ipp new file mode 100644 index 000000000..041961d08 --- /dev/null +++ b/include/bitcoin/database/impl/memory/mmap_staging.ipp @@ -0,0 +1,684 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_STAGING_IPP +#define LIBBITCOIN_DATABASE_MEMORY_MMAP_STAGING_IPP + +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace database { + +// Write-completion accounting (staged instances). Extents chase completions, +// closing the gaps, settling the completed prefix. + +TEMPLATE +void CLASS::complete(size_t + #if defined(MANAGE_STAGING) + offset + #endif + , size_t + #if defined(MANAGE_STAGING) + count + #endif +) NOEXCEPT +{ +#if defined(MANAGE_STAGING) + if (!staged_ || is_zero(count)) + return; + + // Acquire-ordered window snapshot (published entries are immobile; the + // packed word precludes observing a torn head/size pair across pops). + const auto [head, size] = system::unpack_word( + window_.load(std::memory_order_acquire)); + + // Lock-free binary search of the sorted window for the covering extent. + size_t low{}; + auto high = size; + while (low < high) + { + const auto middle = to_half(low + high); + auto& record = ring_.at((head + middle) % extents); + const auto start = record.start.load(std::memory_order_relaxed); + + if (offset < start) + { + high = middle; + continue; + } + + if (offset >= (start + record.count)) + { + low = add1(middle); + continue; + } + + // Wrap from over-completion stalls the frontier (conservative). + const auto previous = record.outstanding.fetch_sub(count, + std::memory_order_relaxed); + + // Repair a decrement landed on a recycled slot (start moved). + if (record.start.load(std::memory_order_relaxed) != start) + { + record.outstanding.fetch_add(count, std::memory_order_relaxed); + return; + } + + // Extent completion is allocation-coarse, so maintenance is cheap + // here and the element write fast path otherwise takes no lock. + if (previous == count) + { + std::unique_lock extent_lock(extent_mutex_, std::try_to_lock); + if (extent_lock.owns_lock()) + maintain_(); + } + + return; + } + + // Contention can record extents out of start order (allocation claim and + // recording are not one atomic step), transiently breaking search order. + // Contiguity guarantees a unique cover, so fall back to a linear scan. + for (size_t index = 0; index < size; ++index) + { + auto& record = ring_.at((head + index) % extents); + const auto start = record.start.load(std::memory_order_relaxed); + + if ((offset < start) || (offset >= (start + record.count))) + continue; + + const auto previous = record.outstanding.fetch_sub(count, + std::memory_order_relaxed); + + if (record.start.load(std::memory_order_relaxed) != start) + { + record.outstanding.fetch_add(count, std::memory_order_relaxed); + return; + } + + if (previous == count) + { + std::unique_lock extent_lock(extent_mutex_, std::try_to_lock); + if (extent_lock.owns_lock()) + maintain_(); + } + + return; + } +#endif +} + +TEMPLATE +size_t CLASS::frontier() const NOEXCEPT +{ +#if defined(MANAGE_STAGING) + if (staged_) + return frontier_.load(); +#endif + + return size(); +} + +#if defined(MANAGE_STAGING) + +TEMPLATE +void CLASS::record_(size_t start, size_t count) NOEXCEPT +{ + if (!staged_ || is_zero(count)) + return; + + std::unique_lock extent_lock(extent_mutex_); + maintain_(); + + // Saturation stalls the frontier (conservative, safe); asserts in debug. + const auto [head, size] = system::unpack_word( + window_.load(std::memory_order_relaxed)); + BC_ASSERT(size < extents); + if (size == extents) + return; + + auto& record = ring_.at((head + size) % extents); + record.start.store(start, std::memory_order_relaxed); + record.count = count; + record.outstanding.store(count * columns, std::memory_order_relaxed); + + // Publish the extent (pairs with the acquire window snapshot). + window_.store(system::pack_word(head, add1(size)), + std::memory_order_release); + + if (is_zero(size)) + frontier_.store(start); +} + +// Pop completed extents from the head, advancing the frontier (locked). +TEMPLATE +void CLASS::maintain_() NOEXCEPT +{ + auto [head, size] = system::unpack_word( + window_.load(std::memory_order_relaxed)); + + while (!is_zero(size) && + is_zero(ring_.at(head).outstanding.load(std::memory_order_relaxed))) + { + head = add1(head) % extents; + --size; + } + + window_.store(system::pack_word(head, size), + std::memory_order_release); + frontier_.store(is_zero(size) ? logical_.load() : + ring_.at(head).start.load(std::memory_order_relaxed)); +} + +// Discard all extents, requires quiescent writers (locked). Any extent then +// outstanding is an abandoned write (unreferenced), safe to settle as is. +TEMPLATE +void CLASS::discard_() NOEXCEPT +{ + if (!staged_) + return; + + std::unique_lock extent_lock(extent_mutex_); + window_.store(zero, std::memory_order_release); + frontier_.store(logical_.load()); +} + +// Delay the caller while staging exceeds its memory bound (write throttle). +// Signaled as the settler drains; a fault or settler stop releases the caller +// into the normal allocation path (which fails on fault/unload). The relieved +// state is lock-free for the common (unparked) case; notifiers synchronize on +// throttle_mutex_ so a parked caller cannot miss its signal. +TEMPLATE +void CLASS::throttle_() NOEXCEPT +{ + const auto relieved = [this]() NOEXCEPT + { + return (system::ceilinged_multiply(system::floored_subtract( + logical_.load(), settled_.load()), stride) <= limit_) || + !settling_.load() || fault_.load(); + }; + + if (!staged_ || relieved()) + return; + + std::unique_lock throttle_lock(throttle_mutex_); + throttle_cv_.wait(throttle_lock, relieved); +} + +// Wake throttled callers, synchronized so a parking caller cannot miss the +// signal between its relieved check and its wait. +TEMPLATE +void CLASS::signal_() NOEXCEPT +{ + std::unique_lock throttle_lock(throttle_mutex_); + throttle_cv_.notify_all(); +} + +// staging dispatch, not thread safe. +// ---------------------------------------------------------------------------- +// private + +TEMPLATE +template +bool CLASS::settle_all_(size_t rows, std::index_sequence) NOEXCEPT +{ + const auto from = settled_.load(); + if (!(settle_(from, rows) && ...)) + return false; + + settled_.store(rows); + signal_(); + return true; +} + +TEMPLATE +template +bool CLASS::unsettle_all_(size_t rows, std::index_sequence) NOEXCEPT +{ + if (!(unsettle_(rows) && ...)) + return false; + + settled_.store(rows); + return true; +} + +// staging wrappers, not thread safe. +// ---------------------------------------------------------------------------- +// private + +// Stage failure results in unmapped. +// Staging has no effect on logical size, commits max(logical, min) capacity. +TEMPLATE +template +bool CLASS::stage_() NOEXCEPT +{ + auto size = logical_.load(); + + // Cannot map empty file, and want minimum capacity, so expand as required. + // disk_full: space is set but no code is set with false return. + if ((size < minimum_) && !resize_(size = minimum_)) + return false; + + // Reserve address space with generous multiple of capacity (costless). + const auto reserved = page_ceiling(to_width(to_reservation(size))); + const auto base = mmap_reserve(reserved); + + if (base == MAP_FAILED) + { + set_first_code(error::mmap_failure); + return false; + } + + memory_map_[Column] = system::pointer_cast(base); + reserved_[Column] = reserved; + + // Commit anonymous pages above the settle boundary page floor. + const auto settled = page_floor(to_width(settled_.load())); + const auto target = to_width(size); + + if ((target > settled) && (mmap_commit(std::next(memory_map_[Column], + settled), target - settled) == fail)) + { + teardown_(error::mmap_failure); + return false; + } + + // Populate anonymous memory from the file (unstaged content in full, + // staged only the settle boundary page remainder). + const auto logical = to_width(logical_.load()); + + if ((settled < logical) && !pread_all(opened_[Column], + std::next(memory_map_[Column], settled), logical - settled, settled)) + { + teardown_(error::fsync_failure); + return false; + } + + // Convert the settled prefix to a read-only file mapping. + if (!settle_(zero, settled_.load())) + return false; + + loaded_.store(true); + return true; +} + +// Commit failure results in unmapped. +// Growth within the reservation commits pages in place (stable map base); an +// exhausted reservation is replaced and its unsettled content copied, under +// the exclusive remap lock held by the caller. +TEMPLATE +template +bool CLASS::commit_(size_t size) NOEXCEPT +{ + const auto target = to_width(size); + + if (target <= reserved_[Column]) + { + // Never commit below the settle boundary page floor (the settled + // prefix is a read-only file mapping); recommit is idempotent. + const auto settled = page_floor(to_width(settled_.load())); + const auto current = page_floor(to_width(capacity_.load())); + const auto from = std::max(settled, current); + + if ((target > from) && (mmap_commit(std::next(memory_map_[Column], + from), target - from) == fail)) + { + teardown_(error::mmap_failure); + return false; + } + + return true; + } + + // Reservation exhausted, so reserve larger and migrate (rare by sizing). + const auto reserved = page_ceiling(to_width(to_reservation(size))); + const auto replace = mmap_reserve(reserved); + + if (replace == MAP_FAILED) + { + teardown_(error::mmap_failure); + return false; + } + + const auto base = system::pointer_cast(replace); + const auto settled = page_floor(to_width(settled_.load())); + + if (mmap_commit(std::next(base, settled), target - settled) == fail) + { + ::munmap(replace, reserved); + teardown_(error::mmap_failure); + return false; + } + + // Copy unsettled content (writes are excluded by the remap lock). + const auto logical = to_width(logical_.load()); + + if (settled < logical) + std::copy_n(std::next(memory_map_[Column], settled), + logical - settled, std::next(base, settled)); + + // Convert the settled prefix on the replacement reservation. + if (!is_zero(settled) && + (mmap_settle(replace, settled, opened_[Column], zero) == fail)) + { + ::munmap(replace, reserved); + teardown_(error::mmap_failure); + return false; + } + +#if !defined(WITHOUT_MADVISE) + if (!is_zero(settled) && !advise_(base, settled)) + { + ::munmap(replace, reserved); + teardown_(error::madvise_failure); + return false; + } +#endif + + // Release the exhausted reservation and adopt the replacement. + const auto released = ::munmap(memory_map_[Column], + reserved_[Column]) != fail; + + memory_map_[Column] = base; + reserved_[Column] = reserved; + + if (!released) + { + set_first_code(error::munmap_failure); + return false; + } + + return true; +} + +// Convert flushed rows [from, to) to a read-only shared file mapping, page +// floored so the settle boundary page remains anonymous with its settled +// bytes retained. Releases the covered anonymous pages. Failure results in +// unmapped. +TEMPLATE +template +bool CLASS::settle_(size_t from, size_t to) NOEXCEPT +{ + if (!staged_) + return true; + + const auto begin = page_floor(to_width(from)); + const auto end = page_floor(to_width(to)); + + if (begin == end) + return true; + + const auto address = std::next(memory_map_[Column], begin); + + if (mmap_settle(address, end - begin, opened_[Column], begin) == fail) + { + teardown_(error::mmap_failure); + return false; + } + +#if !defined(WITHOUT_MADVISE) + if (!advise_(address, end - begin)) + { + teardown_(error::madvise_failure); + return false; + } +#endif + + return true; +} + +// Revert settled pages at/above rows to committed anonymous memory and +// restore the retained bytes below rows from the file (truncation below the +// settle boundary). Failure results in unmapped. +TEMPLATE +template +bool CLASS::unsettle_(size_t rows) NOEXCEPT +{ + const auto bytes = to_width(rows); + const auto begin = page_floor(bytes); + const auto end = page_floor(to_width(settled_.load())); + + if (begin == end) + return true; + + const auto address = std::next(memory_map_[Column], begin); + + if (mmap_unsettle(address, end - begin) == fail) + { + teardown_(error::mmap_failure); + return false; + } + + if ((begin < bytes) && !pread_all(opened_[Column], address, bytes - begin, + begin)) + { + teardown_(error::fsync_failure); + return false; + } + + return true; +} + +// Teardown results in unmapped (release failure is not further reported). +TEMPLATE +template +void CLASS::teardown_(const error::error_t& ec) NOEXCEPT +{ + set_first_code(ec); + + if (!is_null(memory_map_[Column])) + ::munmap(memory_map_[Column], reserved_[Column]); + + memory_map_[Column] = {}; + reserved_[Column] = zero; + loaded_.store(false); +} + +// staging utilities, not thread safe. +// ---------------------------------------------------------------------------- +// private + +#if !defined(WITHOUT_MADVISE) +TEMPLATE +bool CLASS::advise_(uint8_t* map, size_t size) const NOEXCEPT +{ + // Use 1GB chunks to avoid large-length issues. + constexpr auto chunk = system::power2(30u); + const auto advice = random_ ? MADV_RANDOM : MADV_SEQUENTIAL; + + for (auto offset = zero; offset < size; offset += chunk) + { + const auto length = std::min(chunk, size - offset); + if (::madvise(std::next(map, offset), length, advice) == fail) + return false; + } + + return true; +} +#endif // WITHOUT_MADVISE + +// Reservation is address space only (costless), so multiply for headroom; +// exhaustion is handled by reservation replacement. +TEMPLATE +size_t CLASS::to_reservation(size_t rows) const NOEXCEPT +{ + constexpr size_t headroom = 4; + return system::ceilinged_multiply(to_capacity(std::max(rows, minimum_)), + headroom); +} + +TEMPLATE +size_t CLASS::page_floor(size_t bytes) const NOEXCEPT +{ + return system::bit_and(bytes, system::bit_not(sub1(page_))); +} + +TEMPLATE +size_t CLASS::page_ceiling(size_t bytes) const NOEXCEPT +{ + return page_floor(system::ceilinged_add(bytes, sub1(page_))); +} + + + +// settle scheduler, instance-owned (drains completed writes to clean cache). +// ---------------------------------------------------------------------------- +// private + +TEMPLATE +void CLASS::settler_start_() NOEXCEPT +{ + if (!staged_) + return; + + limit_ = system_memory() / 8; + settling_.store(true); + settler_ = std::thread([this]() NOEXCEPT { settler_run_(); }); +} + +TEMPLATE +void CLASS::settler_stop_() NOEXCEPT +{ + if (!settling_.exchange(false)) + return; + + signal_(); + settler_cv_.notify_all(); + if (settler_.joinable()) + settler_.join(); +} + +// Pressure-paced draining (the windows model): drain intensity follows memory +// conditions, settled pages remain cached, writers delay only at the staging +// memory bound (write throttle), sustained stillness drains the residual (the +// lazy writer) so a quiescent map converges to fully settled. +TEMPLATE +void CLASS::settler_run_() NOEXCEPT +{ + // Tiers derive from physical memory and pressure (no configuration). + const auto memory = system_memory(); + const auto urgent = memory / 4; + const auto active = memory / 32; + const auto chunk = std::max(one, (size_t{ 256 } << 20) / stride); + + // Ticks without allocation before idle draining (settling is not writing, + // so draining does not hold its own clock). + constexpr size_t rest = 60; + auto mark = logical_.load(); + size_t still{}; + + const auto backlog = [this]() NOEXCEPT + { + return system::ceilinged_multiply(system::floored_subtract( + frontier_.load(), settled_.load()), stride); + }; + + while (settling_.load()) + { + std::unique_lock settler_lock(settler_mutex_); + settler_cv_.wait_for(settler_lock, std::chrono::seconds(1)); + settler_lock.unlock(); + + if (!settling_.load()) + return; + + const auto top = logical_.load(); + still = (top == mark) ? std::min(add1(still), rest) : zero; + mark = top; + + auto bytes = backlog(); + if (is_zero(bytes)) + continue; + + // Urgency drains continuously, activity/stillness one chunk per tick. + const auto driven = (system_pressure() > one) || (bytes > urgent); + if (!driven && (bytes <= active) && (still < rest)) + continue; + + do + { + if (!settle_next_(chunk)) + break; + + bytes = backlog(); + } + while (settling_.load() && driven && (bytes > active)); + } +} + +// Settle up to chunk completed rows: write under the shared remap lock +// (completed extents are immutable, writers proceed), convert under a brief +// exclusive. Durability remains a snapshot property (no sync here). +TEMPLATE +bool CLASS::settle_next_(size_t chunk) NOEXCEPT +{ + size_t from{}; + size_t target{}; + + { + std::shared_lock map_lock(remap_mutex_); + if (!loaded_.load() || fault_.load()) + return false; + + from = settled_.load(); + target = std::min(frontier_.load(), + system::ceilinged_add(from, chunk)); + + if (target <= from) + return false; + + if (!settle_write_(from, target, sequence{})) + { + set_first_code(error::fsync_failure); + return false; + } + } + + std::unique_lock map_lock(remap_mutex_); + if (!loaded_.load()) + return false; + + // A raced settle boundary (flush) invalidates only this conversion. + if (settled_.load() != from) + return true; + + return settle_all_(target, sequence{}); +} + +TEMPLATE +template +bool CLASS::settle_write_(size_t from, size_t to, + std::index_sequence) NOEXCEPT +{ + return (pwrite_all(opened_[Index], + std::next(memory_map_[Index], to_width(from)), + to_width(to) - to_width(from), + to_width(from)) && ...); +} + +#endif // MANAGE_STAGING + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/memory/mmap_storage.ipp b/include/bitcoin/database/impl/memory/mmap_storage.ipp index 64eb881e1..3774a8d71 100644 --- a/include/bitcoin/database/impl/memory/mmap_storage.ipp +++ b/include/bitcoin/database/impl/memory/mmap_storage.ipp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_DATABASE_MEMORY_MMAP_STORAGE_IPP #define LIBBITCOIN_DATABASE_MEMORY_MMAP_STORAGE_IPP +#include #include #include #include @@ -142,6 +143,11 @@ code CLASS::load() NOEXCEPT } remap_mutex_.unlock(); + +#if defined(MANAGE_STAGING) + settler_start_(); +#endif + return error::success; } @@ -162,6 +168,13 @@ code CLASS::reload() NOEXCEPT return error::reload_unloaded; } +#if defined(MANAGE_STAGING) + // The store reload transactor implies quiescent writers, so remaining + // extents are orphans of abandoned writes, stalling the frontier (in + // any table, not just those that faulted). Discard to unjam settling. + discard_(); +#endif + // Allow resume from disk full. set_disk_space(zero); @@ -176,20 +189,49 @@ code CLASS::reload() NOEXCEPT TEMPLATE code CLASS::flush() NOEXCEPT { - // Prevent unload, resize, remap. - std::shared_lock map_lock(remap_mutex_); + // The suspend-writes contract holds logical_ stable across both phases. + size_t rows{}; + + { + // Prevent unload, resize, remap. + std::shared_lock map_lock(remap_mutex_); + + if (!loaded_.load()) + return error::flush_unloaded; + + rows = logical_.load(); + + // Reads fields and the memory map. + if (!flush_all_(rows, sequence{})) + return error::flush_failure; + } - if (!loaded_.load()) - return error::flush_unloaded; +#if defined(MANAGE_STAGING) + // Convert flushed rows to read-only file mappings, releasing their + // anonymous pages to clean file cache (excludes accessors, briefly). + if (staged_) + { + std::unique_lock map_lock(remap_mutex_); + + if (!loaded_.load()) + return error::flush_unloaded; + + if (!settle_all_(rows, sequence{})) + return error::flush_failure; + } +#endif - // Reads fields and the memory map. - return flush_all_(sequence{}) ? error::success : error::flush_failure; + return error::success; } // Suspend writes before calling. TEMPLATE code CLASS::unload() NOEXCEPT { +#if defined(MANAGE_STAGING) + settler_stop_(); +#endif + std::unique_lock field_lock(field_mutex_); if (remap_mutex_.try_lock()) @@ -218,6 +260,10 @@ code CLASS::unload() NOEXCEPT TEMPLATE code CLASS::shrink() NOEXCEPT { +#if defined(MANAGE_STAGING) + settler_stop_(); +#endif + std::unique_lock field_lock(field_mutex_); if (remap_mutex_.try_lock()) @@ -243,6 +289,11 @@ code CLASS::shrink() NOEXCEPT } remap_mutex_.unlock(); + +#if defined(MANAGE_STAGING) + settler_start_(); +#endif + return error::success; } @@ -283,6 +334,52 @@ bool CLASS::truncate(size_t count) NOEXCEPT if (count > logical_.load()) return false; +#if defined(MANAGE_STAGING) + // Truncation below the settle boundary reverts settled rows to anonymous + // memory, as append-only bodies may re-append after reorganization. + if (staged_ && loaded_.load() && (count < settled_.load())) + { + std::unique_lock remap_lock(remap_mutex_); + + if (!unsettle_all_(count, sequence{})) + return false; + } + + // Discard extents above the truncation and clamp any overlap. + if (staged_) + { + std::unique_lock extent_lock(extent_mutex_); + auto [head, size] = system::unpack_word( + window_.load(std::memory_order_relaxed)); + + while (!is_zero(size)) + { + auto& tail = ring_.at((head + sub1(size)) % extents); + const auto start = tail.start.load(std::memory_order_relaxed); + if (start >= count) + { + --size; + continue; + } + + if (system::ceilinged_add(start, tail.count) > count) + { + tail.count = count - start; + const auto limit = tail.count * columns; + if (tail.outstanding.load(std::memory_order_relaxed) > limit) + tail.outstanding.store(limit, std::memory_order_relaxed); + } + + break; + } + + window_.store(system::pack_word(head, size), + std::memory_order_release); + frontier_.store(is_zero(size) ? count : + ring_.at(head).start.load(std::memory_order_relaxed)); + } +#endif + logical_.store(count); return true; } @@ -344,6 +441,11 @@ bool CLASS::reserve(size_t count) NOEXCEPT TEMPLATE size_t CLASS::allocate(size_t count) NOEXCEPT { +#if defined(MANAGE_STAGING) + // Nothing is held here, so parking cannot deadlock (as with remap waits). + throttle_(); +#endif + // Fast path: claim rows within published capacity (no locks). A failed // exchange implies another claim succeeded, so every retry is progress. for (auto start = logical_.load();;) @@ -356,7 +458,12 @@ size_t CLASS::allocate(size_t count) NOEXCEPT break; if (logical_.compare_exchange_weak(start, start + count)) + { +#if defined(MANAGE_STAGING) + record_(start, count); +#endif return start; + } } // Slow path: serialize capacity growth (at most one grower). Fast paths @@ -372,7 +479,12 @@ size_t CLASS::allocate(size_t count) NOEXCEPT if (end <= capacity_.load()) { if (logical_.compare_exchange_weak(start, end)) + { +#if defined(MANAGE_STAGING) + record_(start, count); +#endif return start; + } continue; } diff --git a/include/bitcoin/database/impl/primitives/arraymap.ipp b/include/bitcoin/database/impl/primitives/arraymap.ipp index 2e92c5105..9c88e2ff6 100644 --- a/include/bitcoin/database/impl/primitives/arraymap.ipp +++ b/include/bitcoin/database/impl/primitives/arraymap.ipp @@ -1,214 +1,218 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP - -#include -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT - : head_(header, buckets), body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::clear() NOEXCEPT -{ - // Head is nullified and its body reference is zeroized. Body memory - // recovery requires truncate/unload/load, which should be preceded by a - // snapshot as all existing snapshots will be invalidated by the truncate. - // An intervening snapshot will capture the zero count body reference (and - // null head links) and will therefore be recoverable whether or not the - // truncation succeeds. - return head_.clear(); -} - -TEMPLATE -bool CLASS::backup(bool prune) NOEXCEPT -{ - return head_.set_body_count(prune ? Link{ 0 } : body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - (count == body_.count()); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::enabled() const NOEXCEPT -{ - return head_.enabled(); -} - -TEMPLATE -size_t CLASS::buckets() const NOEXCEPT -{ - return head_.buckets(); -} - -TEMPLATE -size_t CLASS::head_size() const NOEXCEPT -{ - return head_.size(); -} - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -size_t CLASS::capacity() const NOEXCEPT -{ - return body_.capacity(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -TEMPLATE -bool CLASS::expand(const Link& count) NOEXCEPT -{ - return body_.expand(count); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return body_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -inline Link CLASS::at(size_t key) const NOEXCEPT -{ - return head_.at(key); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::at(size_t key, Element& element) const NOEXCEPT -{ - return get(at(key), element); -} - -TEMPLATE -inline Link CLASS::exists(size_t key) const NOEXCEPT -{ - return !at(key).is_terminal(); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - const auto ptr = body_.get(link); - if (!ptr) - return false; - - using namespace system; - iostream stream{ ptr }; - reader source{ stream }; - - if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } - return element.from_data(source); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::put(size_t key, const Element& element) NOEXCEPT -{ - // Avoid setting at/above terminal sentinel into a bucket position. - if (key >= Link::terminal) - return false; - - const auto link = body_.allocate(element.count()); - const auto ptr = body_.get(link); - if (!ptr) - return false; - - // iostream.flush is a nop (direct copy). - using namespace system; - iostream stream{ ptr }; - finalizer sink{ stream }; - - if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } - return element.to_data(sink) && head_.push(link, head_.index(key)); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAP_IPP + +#include +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::arraymap(storage& header, storage& body, const Link& buckets) NOEXCEPT + : head_(header, buckets), body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::clear() NOEXCEPT +{ + // Head is nullified and its body reference is zeroized. Body memory + // recovery requires truncate/unload/load, which should be preceded by a + // snapshot as all existing snapshots will be invalidated by the truncate. + // An intervening snapshot will capture the zero count body reference (and + // null head links) and will therefore be recoverable whether or not the + // truncation succeeds. + return head_.clear(); +} + +TEMPLATE +bool CLASS::backup(bool prune) NOEXCEPT +{ + return head_.set_body_count(prune ? Link{ 0 } : body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + (count == body_.count()); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::enabled() const NOEXCEPT +{ + return head_.enabled(); +} + +TEMPLATE +size_t CLASS::buckets() const NOEXCEPT +{ + return head_.buckets(); +} + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +size_t CLASS::capacity() const NOEXCEPT +{ + return body_.capacity(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +bool CLASS::expand(const Link& count) NOEXCEPT +{ + return body_.expand(count); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return body_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +inline Link CLASS::at(size_t key) const NOEXCEPT +{ + return head_.at(key); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::at(size_t key, Element& element) const NOEXCEPT +{ + return get(at(key), element); +} + +TEMPLATE +inline Link CLASS::exists(size_t key) const NOEXCEPT +{ + return !at(key).is_terminal(); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + const auto ptr = body_.get(link); + if (!ptr) + return false; + + using namespace system; + iostream stream{ ptr }; + reader source{ stream }; + + if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } + return element.from_data(source); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::put(size_t key, const Element& element) NOEXCEPT +{ + // Avoid setting at/above terminal sentinel into a bucket position. + if (key >= Link::terminal) + return false; + + const auto link = body_.allocate(element.count()); + const auto ptr = body_.get(link); + if (!ptr) + return false; + + // iostream.flush is a nop (direct copy). + using namespace system; + iostream stream{ ptr }; + finalizer sink{ stream }; + + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } + if (!element.to_data(sink) || !head_.push(link, head_.index(key))) + return false; + + body_.complete(link, element.count()); + return true; +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/primitives/body.ipp b/include/bitcoin/database/impl/primitives/body.ipp index b30fff1aa..1c3fa7b4b 100644 --- a/include/bitcoin/database/impl/primitives/body.ipp +++ b/include/bitcoin/database/impl/primitives/body.ipp @@ -1,296 +1,302 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP - -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -template -inline memory CLASS::get() const NOEXCEPT -{ - if constexpr (is_one(columns)) - return files_.get(); - else - return files_.get_at(Column); -} - -TEMPLATE -template -inline memory CLASS::get(const Link& link) const NOEXCEPT -{ - if (link.is_terminal()) - return {}; - - const auto position = link_to_position(link); - - // memory.size() may be negative (stream treats as exhausted). - if constexpr (is_one(columns)) - return files_.get(position); - else - return files_.get_at(Column, position); -} - -TEMPLATE -template -inline memory::iterator CLASS::get_raw1(const Link& link) const NOEXCEPT -{ - if (link.is_terminal()) - return {}; - - const auto position = link_to_position(link); - - // memory.size() may be negative (stream treats as exhausted). - if constexpr (is_one(columns)) - return files_.get_raw(position); - else - return files_.get_raw_at(Column, position); -} - -TEMPLATE -template > -inline memory CLASS::get_capacity(const Link& link) const NOEXCEPT -{ - if (link.is_terminal()) - return {}; - - return files_.get_capacity(link_to_position(link)); -} - -TEMPLATE -CLASS::bodys(storage& body) NOEXCEPT - : files_(body) -{ -} - -TEMPLATE -inline size_t CLASS::size() const NOEXCEPT -{ - if constexpr (is_one(columns)) - return files_.size(); - else - return files_.size() * strides(std::make_index_sequence{}); -} - -TEMPLATE -inline size_t CLASS::capacity() const NOEXCEPT -{ - if constexpr (is_one(columns)) - return files_.capacity(); - else - return files_.capacity() * strides(std::make_index_sequence{}); -} - -TEMPLATE -inline Link CLASS::count() const NOEXCEPT -{ - return elements_to_link(files_.size()); -} - -TEMPLATE -bool CLASS::truncate(const Link& count) NOEXCEPT -{ - if (count.is_terminal()) - return false; - - // Truncate to count visible records (absolute, shared row count). - return files_.truncate(link_to_elements(count)); -} - -TEMPLATE -bool CLASS::expand(const Link& count) NOEXCEPT -{ - if (count.is_terminal()) - return false; - - // Expand to count visible records (absolute, shared row count). - return files_.expand(link_to_elements(count)); -} - -TEMPLATE -bool CLASS::reserve(const Link& count) NOEXCEPT -{ - if (count.is_terminal()) - return false; - - // Expand by count invisible records (relative, shared row count). - return files_.reserve(link_to_elements(count)); -} - -TEMPLATE -Link CLASS::allocate(const Link& count) NOEXCEPT -{ - if (count.is_terminal()) - return count; - - // One shared allocation across all columns (one lock). Expand by count - // visible records (relative), return absolute record link of first. - const auto start = files_.allocate(link_to_elements(count)); - - // Guards addition overflow in cast_link (start must be valid). The eof - // sentinel is in file denomination, so must be detected before conversion. - if (start == storage::eof) - return Link::terminal; - - // Callers (nomaps) handle terminal allocation. - return elements_to_link(start); -} - -// Errors. -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return files_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return files_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return files_.load(); -} - -// static -// ---------------------------------------------------------------------------- - -// private -TEMPLATE -template -constexpr size_t CLASS::stride() NOEXCEPT -{ - using namespace system; - constexpr auto size = std::get(sizes); - - if constexpr (size == max_size_t) - { - // Slab: link/key incorporated into size (byte-addressed map). - return size; - } - else if constexpr (is_zero(Column) && is_nonzero(key_size)) - { - // Spine of a keyed map: link/key precede the record. - return Link::size + key_size + size; - } - else - { - // Keyless (nomap/arraymap) or non-spine column: pure record. - static_assert(is_nonzero(size)); - return size; - } -} - -// private -TEMPLATE -template -constexpr size_t CLASS::strides(std::index_sequence) NOEXCEPT -{ - return (stride() + ...); -} - -TEMPLATE -template -constexpr size_t CLASS::link_to_position(const Link& link) NOEXCEPT -{ - using namespace system; - const auto value = possible_narrow_cast(link.value); - constexpr auto element_size = stride(); - - if constexpr (is_slab) - { - // Slab implies link/key incorporated into size. - return value; - } - else - { - // Record (keyed spine or keyless) implies fixed element stride. - BC_ASSERT(!is_multiply_overflow(value, element_size)); - return value * element_size; - } -} - -TEMPLATE -template -constexpr Link CLASS::position_to_link(size_t position) NOEXCEPT -{ - using namespace system; - constexpr auto element_size = stride(); - - if constexpr (is_slab) - { - // Slab implies link/key incorporated into size. - return { cast_link(position) }; - } - else - { - static_assert(is_nonzero(element_size)); - return { cast_link(position / element_size) }; - } -} - -// private -TEMPLATE -constexpr size_t CLASS::link_to_elements(const Link& link) NOEXCEPT -{ - using namespace system; - - // Single column: file elements are bytes (width one). Records convert - // by stride (slab passes through, slab links are already byte offsets). - // Aggregate: file rows are the shared record count (no conversion). - if constexpr (is_one(columns)) - return link_to_position(link); - else - return possible_narrow_cast(link.value); -} - -// private -TEMPLATE -constexpr Link CLASS::elements_to_link(size_t elements) NOEXCEPT -{ - // Inverse of link_to_elements (slab and aggregate pass through). - if constexpr (is_one(columns)) - return position_to_link(elements); - else - return { cast_link(elements) }; -} - -TEMPLATE -constexpr CLASS::integer CLASS::cast_link(size_t link) NOEXCEPT -{ - using namespace system; - constexpr auto terminal = Link::terminal; - - // link limit is sub1(terminal), where terminal is 2^((8*Link::bytes)-1). - // It is ok for the payload to exceed link limit (link is identity only). - return link >= terminal ? terminal : possible_narrow_cast(link); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_IPP + +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +template +inline memory CLASS::get() const NOEXCEPT +{ + if constexpr (is_one(columns)) + return files_.get(); + else + return files_.get_at(Column); +} + +TEMPLATE +template +inline memory CLASS::get(const Link& link) const NOEXCEPT +{ + if (link.is_terminal()) + return {}; + + const auto position = link_to_position(link); + + // memory.size() may be negative (stream treats as exhausted). + if constexpr (is_one(columns)) + return files_.get(position); + else + return files_.get_at(Column, position); +} + +TEMPLATE +template +inline memory::iterator CLASS::get_raw1(const Link& link) const NOEXCEPT +{ + if (link.is_terminal()) + return {}; + + const auto position = link_to_position(link); + + // memory.size() may be negative (stream treats as exhausted). + if constexpr (is_one(columns)) + return files_.get_raw(position); + else + return files_.get_raw_at(Column, position); +} + +TEMPLATE +template > +inline memory CLASS::get_capacity(const Link& link) const NOEXCEPT +{ + if (link.is_terminal()) + return {}; + + return files_.get_capacity(link_to_position(link)); +} + +TEMPLATE +CLASS::bodys(storage& body) NOEXCEPT + : files_(body) +{ +} + +TEMPLATE +inline size_t CLASS::size() const NOEXCEPT +{ + if constexpr (is_one(columns)) + return files_.size(); + else + return files_.size() * strides(std::make_index_sequence{}); +} + +TEMPLATE +inline size_t CLASS::capacity() const NOEXCEPT +{ + if constexpr (is_one(columns)) + return files_.capacity(); + else + return files_.capacity() * strides(std::make_index_sequence{}); +} + +TEMPLATE +inline Link CLASS::count() const NOEXCEPT +{ + return elements_to_link(files_.size()); +} + +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + if (count.is_terminal()) + return false; + + // Truncate to count visible records (absolute, shared row count). + return files_.truncate(link_to_elements(count)); +} + +TEMPLATE +bool CLASS::expand(const Link& count) NOEXCEPT +{ + if (count.is_terminal()) + return false; + + // Expand to count visible records (absolute, shared row count). + return files_.expand(link_to_elements(count)); +} + +TEMPLATE +bool CLASS::reserve(const Link& count) NOEXCEPT +{ + if (count.is_terminal()) + return false; + + // Expand by count invisible records (relative, shared row count). + return files_.reserve(link_to_elements(count)); +} + +TEMPLATE +Link CLASS::allocate(const Link& count) NOEXCEPT +{ + if (count.is_terminal()) + return count; + + // One shared allocation across all columns (one lock). Expand by count + // visible records (relative), return absolute record link of first. + const auto start = files_.allocate(link_to_elements(count)); + + // Guards addition overflow in cast_link (start must be valid). The eof + // sentinel is in file denomination, so must be detected before conversion. + if (start == storage::eof) + return Link::terminal; + + // Callers (nomaps) handle terminal allocation. + return elements_to_link(start); +} + +TEMPLATE +void CLASS::complete(const Link& link, const Link& count) NOEXCEPT +{ + files_.complete(link_to_elements(link), link_to_elements(count)); +} + +// Errors. +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return files_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return files_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return files_.load(); +} + +// static +// ---------------------------------------------------------------------------- + +// private +TEMPLATE +template +constexpr size_t CLASS::stride() NOEXCEPT +{ + using namespace system; + constexpr auto size = std::get(sizes); + + if constexpr (size == max_size_t) + { + // Slab: link/key incorporated into size (byte-addressed map). + return size; + } + else if constexpr (is_zero(Column) && is_nonzero(key_size)) + { + // Spine of a keyed map: link/key precede the record. + return Link::size + key_size + size; + } + else + { + // Keyless (nomap/arraymap) or non-spine column: pure record. + static_assert(is_nonzero(size)); + return size; + } +} + +// private +TEMPLATE +template +constexpr size_t CLASS::strides(std::index_sequence) NOEXCEPT +{ + return (stride() + ...); +} + +TEMPLATE +template +constexpr size_t CLASS::link_to_position(const Link& link) NOEXCEPT +{ + using namespace system; + const auto value = possible_narrow_cast(link.value); + constexpr auto element_size = stride(); + + if constexpr (is_slab) + { + // Slab implies link/key incorporated into size. + return value; + } + else + { + // Record (keyed spine or keyless) implies fixed element stride. + BC_ASSERT(!is_multiply_overflow(value, element_size)); + return value * element_size; + } +} + +TEMPLATE +template +constexpr Link CLASS::position_to_link(size_t position) NOEXCEPT +{ + using namespace system; + constexpr auto element_size = stride(); + + if constexpr (is_slab) + { + // Slab implies link/key incorporated into size. + return { cast_link(position) }; + } + else + { + static_assert(is_nonzero(element_size)); + return { cast_link(position / element_size) }; + } +} + +// private +TEMPLATE +constexpr size_t CLASS::link_to_elements(const Link& link) NOEXCEPT +{ + using namespace system; + + // Single column: file elements are bytes (width one). Records convert + // by stride (slab passes through, slab links are already byte offsets). + // Aggregate: file rows are the shared record count (no conversion). + if constexpr (is_one(columns)) + return link_to_position(link); + else + return possible_narrow_cast(link.value); +} + +// private +TEMPLATE +constexpr Link CLASS::elements_to_link(size_t elements) NOEXCEPT +{ + // Inverse of link_to_elements (slab and aggregate pass through). + if constexpr (is_one(columns)) + return position_to_link(elements); + else + return { cast_link(elements) }; +} + +TEMPLATE +constexpr CLASS::integer CLASS::cast_link(size_t link) NOEXCEPT +{ + using namespace system; + constexpr auto terminal = Link::terminal; + + // link limit is sub1(terminal), where terminal is 2^((8*Link::bytes)-1). + // It is ok for the payload to exceed link limit (link is identity only). + return link >= terminal ? terminal : possible_narrow_cast(link); +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/primitives/hashmap.ipp b/include/bitcoin/database/impl/primitives/hashmap.ipp index 3d1087c65..fad23f867 100644 --- a/include/bitcoin/database/impl/primitives/hashmap.ipp +++ b/include/bitcoin/database/impl/primitives/hashmap.ipp @@ -1,567 +1,577 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP - -#include -#include -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::hashmap(storage& header, storage& body, const Link& buckets) NOEXCEPT - : head_(header, buckets), body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::backup(bool) NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - (count == body_.count()); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::enabled() const NOEXCEPT -{ - return head_.buckets() > one; -} - -TEMPLATE -size_t CLASS::buckets() const NOEXCEPT -{ - return head_.buckets(); -} - -TEMPLATE -size_t CLASS::head_size() const NOEXCEPT -{ - return head_.size(); -} - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -size_t CLASS::capacity() const NOEXCEPT -{ - return body_.capacity(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -TEMPLATE -bool CLASS::expand(const Link& count) NOEXCEPT -{ - return body_.expand(count); -} - -// diagnostic counters -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::positive_search_count() const NOEXCEPT -{ - return positive_.load(std::memory_order_relaxed); -} - -TEMPLATE -size_t CLASS::negative_search_count() const NOEXCEPT -{ - return negative_.load(std::memory_order_relaxed); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return body_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -inline Link CLASS::top(const Link& link) const NOEXCEPT -{ - if (link >= head_.buckets()) - return {}; - - return head_.top(link); -} - -TEMPLATE -inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT -{ - return !first(ptr, key).is_terminal(); -} - -TEMPLATE -inline bool CLASS::exists(const Key& key) const NOEXCEPT -{ - return !first(key).is_terminal(); -} - -TEMPLATE -inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT -{ - return first(ptr, head_.top(key), key); -} - -TEMPLATE -inline Link CLASS::first(const Key& key) const NOEXCEPT -{ - return first(get_memory(), key); -} - -TEMPLATE -inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT -{ - const auto top = head_.top(key); - return { get_memory(), top, std::forward(key) }; -} - -TEMPLATE -inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT -{ - return { get_memory(), head_.top(key), key }; -} - -TEMPLATE -inline Link CLASS::allocate(const Link& size) NOEXCEPT -{ - return body_.allocate(size); -} - -TEMPLATE -inline memory CLASS::get_memory() const NOEXCEPT -{ - return body_.get(); -} - -TEMPLATE -Key CLASS::get_key(const Link& link) NOEXCEPT -{ - using namespace system; - const auto ptr = body_.get(link); - if (!ptr || is_lesser(ptr.size(), index_size)) - return {}; - - return unsafe_array_cast(std::next(ptr.begin(), - Link::size)); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT -{ - return !find_link(key, element).is_terminal(); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT -{ - // This override avoids duplicated memory construct in get(first()). - const auto ptr = get_memory(); - const auto link = first(ptr, head_.top(key), key); - if (link.is_terminal()) - return {}; - - return read(ptr, link, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - // This override is the normal form. - return read(get_memory(), link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const memory& ptr, const Link& link, - Element& element) NOEXCEPT -{ - return read(ptr, link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT -{ - // This override avoids deadlock when holding iterator to the same table. - return read(it.ptr(), *it, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const iterator& it, const Link& link, - Element& element) NOEXCEPT -{ - // This override avoids deadlock when holding iterator to the same table. - return read(it.ptr(), link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::set(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - // Stream starts at record and the index is skipped for reader convenience. - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // Set element search key. - unsafe_array_cast(std::next(offset, - Link::size)) = key; - - iostream stream{ offset, size - position }; - finalizer sink{ stream }; - sink.skip_bytes(index_size); - - if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } - return element.to_data(sink); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::set(const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - return set(get_memory(), link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT -{ - Link link{}; - if (!set_link(link, key, element)) - return {}; - - return link; -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::set_link(Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - link = allocate(element.count()); - return set(link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT -{ - Link link{}; - if (!put_link(link, key, element)) - return {}; - - return link; -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put_link(Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - link = allocate(element.count()); - return put(link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT -{ - return !put_link(key, element).is_terminal(); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - // This override is the normal form. - return write(get_memory(), link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - return write(ptr, link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(bool& duplicate, const memory& ptr, - const Link& link, const Key& key, const Element& element) NOEXCEPT -{ - Link previous{}; - if (!write(previous, ptr, link, key, element)) - return false; - - if (previous.is_terminal()) - { - duplicate = false; - ////negative_.fetch_add(one, std::memory_order_relaxed); - } - else - { - // Search the previous conflicts to determine if actual duplicate. - duplicate = !first(ptr, previous, key).is_terminal(); - ////positive_.fetch_add(one, std::memory_order_relaxed); - } - - return true; -} - -TEMPLATE -inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT -{ - if (!commit(link, key)) - return {}; - - return link; -} - -TEMPLATE -inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT -{ - return commit(get_memory(), link, key); -} - -TEMPLATE -bool CLASS::commit(const memory& ptr, const Link& link, - const Key& key) NOEXCEPT -{ - using namespace system; - if (!ptr) - return false; - - // get element offset (fault) - const auto offset = ptr.offset(body::link_to_position(link)); - if (is_null(offset)) - return false; - - // Commit element to search index (terminal is a valid bucket index). - auto& next = unsafe_array_cast(offset); - return head_.push(link, next, key); -} - -// protected -// ---------------------------------------------------------------------------- - -// static -TEMPLATE -Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT -{ - using namespace system; - if (!ptr) - return {}; - - auto next = link; - while (!next.is_terminal()) - { - // get element offset (fault) - const auto offset = ptr.offset(body::link_to_position(next)); - if (is_null(offset)) - return {}; - - // element key matches (found) - if (keys::compare(unsafe_array_cast( - std::next(offset, Link::size)), key)) - return next; - - // set next element link (loop) - next = unsafe_array_cast(offset); - } - - return next; -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // Stream starts at record and the index is skipped for reader convenience. - iostream stream{ offset, size - position }; - reader source{ stream }; - source.skip_bytes(index_size); - - if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } - return element.from_data(source); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::write(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - Link unused{}; - return write(unused, ptr, link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::write(Link& previous, const memory& ptr, const Link& link, - const Key& key, const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // iostream.flush is a nop (direct copy). - iostream stream{ offset, size - position }; - finalizer sink{ stream }; - sink.skip_bytes(Link::size); - keys::write(sink, key); - - // Commit element to body. - if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } - auto& next = unsafe_array_cast(offset); - if (!element.to_data(sink)) - return false; - - // Commit element to search (terminal is a valid bucket index). - bool search{}; - if (!head_.push(search, link, next, key)) - return false; - - // If collision set previous stack head for conflict resolution search. - previous = search ? Link{ next } : Link{}; - return true; -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP + +#include +#include +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::hashmap(storage& header, storage& body, const Link& buckets) NOEXCEPT + : head_(header, buckets), body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + (count == body_.count()); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::enabled() const NOEXCEPT +{ + return head_.buckets() > one; +} + +TEMPLATE +size_t CLASS::buckets() const NOEXCEPT +{ + return head_.buckets(); +} + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +size_t CLASS::capacity() const NOEXCEPT +{ + return body_.capacity(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +bool CLASS::expand(const Link& count) NOEXCEPT +{ + return body_.expand(count); +} + +// diagnostic counters +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::positive_search_count() const NOEXCEPT +{ + return positive_.load(std::memory_order_relaxed); +} + +TEMPLATE +size_t CLASS::negative_search_count() const NOEXCEPT +{ + return negative_.load(std::memory_order_relaxed); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return body_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +inline Link CLASS::top(const Link& link) const NOEXCEPT +{ + if (link >= head_.buckets()) + return {}; + + return head_.top(link); +} + +TEMPLATE +inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT +{ + return !first(ptr, key).is_terminal(); +} + +TEMPLATE +inline bool CLASS::exists(const Key& key) const NOEXCEPT +{ + return !first(key).is_terminal(); +} + +TEMPLATE +inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT +{ + return first(ptr, head_.top(key), key); +} + +TEMPLATE +inline Link CLASS::first(const Key& key) const NOEXCEPT +{ + return first(get_memory(), key); +} + +TEMPLATE +inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT +{ + const auto top = head_.top(key); + return { get_memory(), top, std::forward(key) }; +} + +TEMPLATE +inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT +{ + return { get_memory(), head_.top(key), key }; +} + +TEMPLATE +inline Link CLASS::allocate(const Link& size) NOEXCEPT +{ + return body_.allocate(size); +} + +TEMPLATE +inline memory CLASS::get_memory() const NOEXCEPT +{ + return body_.get(); +} + +TEMPLATE +Key CLASS::get_key(const Link& link) NOEXCEPT +{ + using namespace system; + const auto ptr = body_.get(link); + if (!ptr || is_lesser(ptr.size(), index_size)) + return {}; + + return unsafe_array_cast(std::next(ptr.begin(), + Link::size)); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT +{ + return !find_link(key, element).is_terminal(); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT +{ + // This override avoids duplicated memory construct in get(first()). + const auto ptr = get_memory(); + const auto link = first(ptr, head_.top(key), key); + if (link.is_terminal()) + return {}; + + return read(ptr, link, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + // This override is the normal form. + return read(get_memory(), link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const memory& ptr, const Link& link, + Element& element) NOEXCEPT +{ + return read(ptr, link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT +{ + // This override avoids deadlock when holding iterator to the same table. + return read(it.ptr(), *it, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const iterator& it, const Link& link, + Element& element) NOEXCEPT +{ + // This override avoids deadlock when holding iterator to the same table. + return read(it.ptr(), link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::set(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + // Stream starts at record and the index is skipped for reader convenience. + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // Set element search key. + unsafe_array_cast(std::next(offset, + Link::size)) = key; + + iostream stream{ offset, size - position }; + finalizer sink{ stream }; + sink.skip_bytes(index_size); + + // Due to accounting, record hashmaps are limited to set(1). + if constexpr (!is_slab) { BC_ASSERT(is_one(element.count())); } + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize);) } + return element.to_data(sink); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::set(const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + return set(get_memory(), link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT +{ + Link link{}; + if (!set_link(link, key, element)) + return {}; + + return link; +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::set_link(Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + link = allocate(element.count()); + return set(link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT +{ + Link link{}; + if (!put_link(link, key, element)) + return {}; + + return link; +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put_link(Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + link = allocate(element.count()); + return put(link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT +{ + return !put_link(key, element).is_terminal(); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + // This override is the normal form. + return write(get_memory(), link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + return write(ptr, link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(bool& duplicate, const memory& ptr, + const Link& link, const Key& key, const Element& element) NOEXCEPT +{ + Link previous{}; + if (!write(previous, ptr, link, key, element)) + return false; + + if (previous.is_terminal()) + { + duplicate = false; + ////negative_.fetch_add(one, std::memory_order_relaxed); + } + else + { + // Search the previous conflicts to determine if actual duplicate. + duplicate = !first(ptr, previous, key).is_terminal(); + ////positive_.fetch_add(one, std::memory_order_relaxed); + } + + return true; +} + +TEMPLATE +inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT +{ + if (!commit(link, key)) + return {}; + + return link; +} + +TEMPLATE +inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT +{ + return commit(get_memory(), link, key); +} + +TEMPLATE +bool CLASS::commit(const memory& ptr, const Link& link, + const Key& key) NOEXCEPT +{ + using namespace system; + if (!ptr) + return false; + + // get element offset (fault) + const auto offset = ptr.offset(body::link_to_position(link)); + if (is_null(offset)) + return false; + + // Commit element to search index (terminal is a valid bucket index). + auto& next = unsafe_array_cast(offset); + if (!head_.push(link, next, key)) + return false; + + // set/commit is limited to record tables, one row at a time. + body_.complete(link, one); + return true; +} + +// protected +// ---------------------------------------------------------------------------- + +// static +TEMPLATE +Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT +{ + using namespace system; + if (!ptr) + return {}; + + auto next = link; + while (!next.is_terminal()) + { + // get element offset (fault) + const auto offset = ptr.offset(body::link_to_position(next)); + if (is_null(offset)) + return {}; + + // element key matches (found) + if (keys::compare(unsafe_array_cast( + std::next(offset, Link::size)), key)) + return next; + + // set next element link (loop) + next = unsafe_array_cast(offset); + } + + return next; +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // Stream starts at record and the index is skipped for reader convenience. + iostream stream{ offset, size - position }; + reader source{ stream }; + source.skip_bytes(index_size); + + if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } + return element.from_data(source); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::write(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + Link unused{}; + return write(unused, ptr, link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::write(Link& previous, const memory& ptr, const Link& link, + const Key& key, const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // iostream.flush is a nop (direct copy). + iostream stream{ offset, size - position }; + finalizer sink{ stream }; + sink.skip_bytes(Link::size); + keys::write(sink, key); + + // Commit element to body. + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } + auto& next = unsafe_array_cast(offset); + if (!element.to_data(sink)) + return false; + + // Commit element to search (terminal is a valid bucket index). + bool search{}; + if (!head_.push(search, link, next, key)) + return false; + + // If collision set previous stack head for conflict resolution search. + previous = search ? Link{ next } : Link{}; + + // Report element write completion (count matches its allocation). + body_.complete(link, element.count()); + return true; +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/primitives/hashmaps.ipp b/include/bitcoin/database/impl/primitives/hashmaps.ipp index 48567e855..a2ae94263 100644 --- a/include/bitcoin/database/impl/primitives/hashmaps.ipp +++ b/include/bitcoin/database/impl/primitives/hashmaps.ipp @@ -1,631 +1,645 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP - -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::hashmaps(storage& header, storage& body, const Link& buckets) NOEXCEPT - : head_(header, buckets), body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::backup(bool) NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && head_.get_body_count(count) && - (count == body_.count()); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::enabled() const NOEXCEPT -{ - return head_.buckets() > one; -} - -TEMPLATE -size_t CLASS::buckets() const NOEXCEPT -{ - return head_.buckets(); -} - -TEMPLATE -size_t CLASS::head_size() const NOEXCEPT -{ - return head_.size(); -} - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -size_t CLASS::capacity() const NOEXCEPT -{ - return body_.capacity(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -// diagnostic counters -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::positive_search_count() const NOEXCEPT -{ - return positive_.load(std::memory_order_relaxed); -} - -TEMPLATE -size_t CLASS::negative_search_count() const NOEXCEPT -{ - return negative_.load(std::memory_order_relaxed); -} - -// error condition -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return body_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -inline Link CLASS::top(const Link& link) const NOEXCEPT -{ - if (link >= head_.buckets()) - return {}; - - return head_.top(link); -} - -TEMPLATE -inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT -{ - return !first(ptr, key).is_terminal(); -} - -TEMPLATE -inline bool CLASS::exists(const Key& key) const NOEXCEPT -{ - return !first(key).is_terminal(); -} - -TEMPLATE -inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT -{ - return first(ptr, head_.top(key), key); -} - -TEMPLATE -inline Link CLASS::first(const Key& key) const NOEXCEPT -{ - return first(get_memory(), key); -} - -TEMPLATE -inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT -{ - const auto top = head_.top(key); - return { get_memory(), top, std::forward(key) }; -} - -TEMPLATE -inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT -{ - return { get_memory(), head_.top(key), key }; -} - -TEMPLATE -inline Link CLASS::allocate(const Link& size) NOEXCEPT -{ - return body_.allocate(size); -} - -TEMPLATE -template -inline memory CLASS::get_memory() const NOEXCEPT -{ - return body_.template get(); -} - -TEMPLATE -Key CLASS::get_key(const Link& link) NOEXCEPT -{ - using namespace system; - const auto ptr = body_.get(link); - if (!ptr || is_lesser(ptr.size(), index_size)) - return {}; - - return unsafe_array_cast(std::next(ptr.begin(), - Link::size)); -} - -// spine (column zero) -// ---------------------------------------------------------------------------- - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT -{ - return !find_link(key, element).is_terminal(); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT -{ - // This override avoids duplicated memory construct in get(first()). - const auto ptr = get_memory(); - const auto link = first(ptr, head_.top(key), key); - if (link.is_terminal()) - return {}; - - return read(ptr, link, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - // This override is the normal form. - return read(get_memory(), link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const memory& ptr, const Link& link, - Element& element) NOEXCEPT -{ - return read(ptr, link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT -{ - // This override avoids deadlock when holding iterator to the same table. - return read(it.ptr(), *it, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::get(const iterator& it, const Link& link, - Element& element) NOEXCEPT -{ - // This override avoids deadlock when holding iterator to the same table. - return read(it.ptr(), link, element); -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::set(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - // Stream starts at record and the index is skipped for reader convenience. - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // Set element search key. - unsafe_array_cast(std::next(offset, - Link::size)) = key; - - iostream stream{ offset, size - position }; - finalizer sink{ stream }; - sink.skip_bytes(index_size); - - if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } - return element.to_data(sink); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::set(const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - return set(get_memory(), link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT -{ - Link link{}; - if (!set_link(link, key, element)) - return {}; - - return link; -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::set_link(Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - link = allocate(element.count()); - return set(link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT -{ - Link link{}; - if (!put_link(link, key, element)) - return {}; - - return link; -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put_link(Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - link = allocate(element.count()); - return put(link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT -{ - return !put_link(key, element).is_terminal(); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - // This override is the normal form. - return write(get_memory(), link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - return write(ptr, link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -inline bool CLASS::put(bool& duplicate, const memory& ptr, - const Link& link, const Key& key, const Element& element) NOEXCEPT -{ - Link previous{}; - if (!write(previous, ptr, link, key, element)) - return false; - - if (previous.is_terminal()) - { - duplicate = false; - ////negative_.fetch_add(one, std::memory_order_relaxed); - } - else - { - // Search the previous conflicts to determine if actual duplicate. - duplicate = !first(ptr, previous, key).is_terminal(); - ////positive_.fetch_add(one, std::memory_order_relaxed); - } - - return true; -} - -TEMPLATE -inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT -{ - if (!commit(link, key)) - return {}; - - return link; -} - -TEMPLATE -inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT -{ - return commit(get_memory(), link, key); -} - -TEMPLATE -bool CLASS::commit(const memory& ptr, const Link& link, - const Key& key) NOEXCEPT -{ - using namespace system; - if (!ptr) - return false; - - // get element offset (fault) - const auto offset = ptr.offset(body::link_to_position(link)); - if (is_null(offset)) - return false; - - // Commit element to search index (terminal is a valid bucket index). - auto& next = unsafe_array_cast(offset); - return head_.push(link, next, key); -} - -// satellites (nonzero Column) -// ---------------------------------------------------------------------------- - -// static -TEMPLATE -template -bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - static_assert(is_nonzero(Column), "column zero is the keyed spine"); - static_assert(Element::size == width); - if (!ptr || link.is_terminal()) - return false; - - using namespace system; - const auto start = body::template link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - iostream stream{ offset, size - position }; - reader source{ stream }; - - BC_DEBUG_ONLY(source.set_limit(width * element.count());) - return element.from_data(source); -} - -TEMPLATE -template -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - return get(get_memory(), link, element); -} - -TEMPLATE -template -bool CLASS::put(const Link& link, const Element& element) NOEXCEPT -{ - const auto ptr = body_.template get_raw1(link); - return put(ptr, element); -} - -// protected (unguarded memory access) -TEMPLATE -template -bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT -{ - static_assert(is_nonzero(Column), "column zero is the keyed spine"); - static_assert(Element::size == width); - if (is_null(it)) - return false; - - using namespace system; - const auto bytes = width * element.count(); - iostream stream{ it, possible_narrow_and_sign_cast(bytes) }; - flipper sink{ stream }; - - BC_DEBUG_ONLY(sink.set_limit(width * element.count());) - return element.to_data(sink); -} - -// protected -// ---------------------------------------------------------------------------- - -// static -TEMPLATE -Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT -{ - using namespace system; - if (!ptr) - return {}; - - auto next = link; - while (!next.is_terminal()) - { - // get element offset (fault) - const auto offset = ptr.offset(body::link_to_position(next)); - if (is_null(offset)) - return {}; - - // element key matches (found) - if (keys::compare(unsafe_array_cast( - std::next(offset, Link::size)), key)) - return next; - - // set next element link (loop) - next = unsafe_array_cast(offset); - } - - return next; -} - -// static -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // Stream starts at record and the index is skipped for reader convenience. - iostream stream{ offset, size - position }; - reader source{ stream }; - source.skip_bytes(index_size); - - if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } - return element.from_data(source); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::write(const memory& ptr, const Link& link, const Key& key, - const Element& element) NOEXCEPT -{ - Link unused{}; - return write(unused, ptr, link, key, element); -} - -TEMPLATE -ELEMENT_CONSTRAINT -bool CLASS::write(Link& previous, const memory& ptr, const Link& link, - const Key& key, const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - // iostream.flush is a nop (direct copy). - iostream stream{ offset, size - position }; - finalizer sink{ stream }; - sink.skip_bytes(Link::size); - keys::write(sink, key); - - // Commit element to body. - if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } - auto& next = unsafe_array_cast(offset); - if (!element.to_data(sink)) - return false; - - // Commit element to search (terminal is a valid bucket index). - bool search{}; - if (!head_.push(search, link, next, key)) - return false; - - // If collision set previous stack head for conflict resolution search. - previous = search ? Link{ next } : Link{}; - return true; -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAPS_IPP + +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::hashmaps(storage& header, storage& body, const Link& buckets) NOEXCEPT + : head_(header, buckets), body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && head_.get_body_count(count) && + (count == body_.count()); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::enabled() const NOEXCEPT +{ + return head_.buckets() > one; +} + +TEMPLATE +size_t CLASS::buckets() const NOEXCEPT +{ + return head_.buckets(); +} + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +size_t CLASS::capacity() const NOEXCEPT +{ + return body_.capacity(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +// diagnostic counters +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::positive_search_count() const NOEXCEPT +{ + return positive_.load(std::memory_order_relaxed); +} + +TEMPLATE +size_t CLASS::negative_search_count() const NOEXCEPT +{ + return negative_.load(std::memory_order_relaxed); +} + +// error condition +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return body_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +inline Link CLASS::top(const Link& link) const NOEXCEPT +{ + if (link >= head_.buckets()) + return {}; + + return head_.top(link); +} + +TEMPLATE +inline bool CLASS::exists(const memory& ptr, const Key& key) const NOEXCEPT +{ + return !first(ptr, key).is_terminal(); +} + +TEMPLATE +inline bool CLASS::exists(const Key& key) const NOEXCEPT +{ + return !first(key).is_terminal(); +} + +TEMPLATE +inline Link CLASS::first(const memory& ptr, const Key& key) const NOEXCEPT +{ + return first(ptr, head_.top(key), key); +} + +TEMPLATE +inline Link CLASS::first(const Key& key) const NOEXCEPT +{ + return first(get_memory(), key); +} + +TEMPLATE +inline typename CLASS::iterator CLASS::it(Key&& key) const NOEXCEPT +{ + const auto top = head_.top(key); + return { get_memory(), top, std::forward(key) }; +} + +TEMPLATE +inline typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT +{ + return { get_memory(), head_.top(key), key }; +} + +TEMPLATE +inline Link CLASS::allocate(const Link& size) NOEXCEPT +{ + return body_.allocate(size); +} + +TEMPLATE +template +inline memory CLASS::get_memory() const NOEXCEPT +{ + return body_.template get(); +} + +TEMPLATE +Key CLASS::get_key(const Link& link) NOEXCEPT +{ + using namespace system; + const auto ptr = body_.get(link); + if (!ptr || is_lesser(ptr.size(), index_size)) + return {}; + + return unsafe_array_cast(std::next(ptr.begin(), + Link::size)); +} + +// spine (column zero) +// ---------------------------------------------------------------------------- + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::find(const Key& key, Element& element) const NOEXCEPT +{ + return !find_link(key, element).is_terminal(); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::find_link(const Key& key, Element& element) const NOEXCEPT +{ + // This override avoids duplicated memory construct in get(first()). + const auto ptr = get_memory(); + const auto link = first(ptr, head_.top(key), key); + if (link.is_terminal()) + return {}; + + return read(ptr, link, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + // This override is the normal form. + return read(get_memory(), link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const memory& ptr, const Link& link, + Element& element) NOEXCEPT +{ + return read(ptr, link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const iterator& it, Element& element) NOEXCEPT +{ + // This override avoids deadlock when holding iterator to the same table. + return read(it.ptr(), *it, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::get(const iterator& it, const Link& link, + Element& element) NOEXCEPT +{ + // This override avoids deadlock when holding iterator to the same table. + return read(it.ptr(), link, element); +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::set(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + // Stream starts at record and the index is skipped for reader convenience. + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // Set element search key. + unsafe_array_cast(std::next(offset, + Link::size)) = key; + + iostream stream{ offset, size - position }; + finalizer sink{ stream }; + sink.skip_bytes(index_size); + + // Due to accounting, record hashmaps are limited to set(1). + if constexpr (!is_slab) { BC_ASSERT(is_one(element.count())); } + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize);) } + return element.to_data(sink); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::set(const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + return set(get_memory(), link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::set_link(const Key& key, const Element& element) NOEXCEPT +{ + Link link{}; + if (!set_link(link, key, element)) + return {}; + + return link; +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::set_link(Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + link = allocate(element.count()); + return set(link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT +{ + Link link{}; + if (!put_link(link, key, element)) + return {}; + + return link; +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put_link(Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + link = allocate(element.count()); + return put(link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const Key& key, const Element& element) NOEXCEPT +{ + return !put_link(key, element).is_terminal(); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + // This override is the normal form. + return write(get_memory(), link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + return write(ptr, link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +inline bool CLASS::put(bool& duplicate, const memory& ptr, + const Link& link, const Key& key, const Element& element) NOEXCEPT +{ + Link previous{}; + if (!write(previous, ptr, link, key, element)) + return false; + + if (previous.is_terminal()) + { + duplicate = false; + ////negative_.fetch_add(one, std::memory_order_relaxed); + } + else + { + // Search the previous conflicts to determine if actual duplicate. + duplicate = !first(ptr, previous, key).is_terminal(); + ////positive_.fetch_add(one, std::memory_order_relaxed); + } + + return true; +} + +TEMPLATE +inline Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT +{ + if (!commit(link, key)) + return {}; + + return link; +} + +TEMPLATE +inline bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT +{ + return commit(get_memory(), link, key); +} + +TEMPLATE +bool CLASS::commit(const memory& ptr, const Link& link, + const Key& key) NOEXCEPT +{ + using namespace system; + if (!ptr) + return false; + + // get element offset (fault) + const auto offset = ptr.offset(body::link_to_position(link)); + if (is_null(offset)) + return false; + + // Commit element to search index (terminal is a valid bucket index). + auto& next = unsafe_array_cast(offset); + if (!head_.push(link, next, key)) + return false; + + // set/commit is limited to record tables, one row at a time. + body_.complete(link, one); + return true; +} + +// satellites (nonzero Column) +// ---------------------------------------------------------------------------- + +// static +TEMPLATE +template +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + static_assert(is_nonzero(Column), "column zero is the keyed spine"); + static_assert(Element::size == width); + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = body::template link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + reader source{ stream }; + + BC_DEBUG_ONLY(source.set_limit(width * element.count());) + return element.from_data(source); +} + +TEMPLATE +template +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + return get(get_memory(), link, element); +} + +TEMPLATE +template +bool CLASS::put(const Link& link, const Element& element) NOEXCEPT +{ + const auto ptr = body_.template get_raw1(link); + if (!put(ptr, element)) + return false; + + body_.complete(link, element.count()); + return true; +} + +// protected (unguarded memory access) +TEMPLATE +template +bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT +{ + static_assert(is_nonzero(Column), "column zero is the keyed spine"); + static_assert(Element::size == width); + if (is_null(it)) + return false; + + using namespace system; + const auto bytes = width * element.count(); + iostream stream{ it, possible_narrow_and_sign_cast(bytes) }; + flipper sink{ stream }; + + BC_DEBUG_ONLY(sink.set_limit(width * element.count());) + return element.to_data(sink); +} + +// protected +// ---------------------------------------------------------------------------- + +// static +TEMPLATE +Link CLASS::first(const memory& ptr, const Link& link, const Key& key) NOEXCEPT +{ + using namespace system; + if (!ptr) + return {}; + + auto next = link; + while (!next.is_terminal()) + { + // get element offset (fault) + const auto offset = ptr.offset(body::link_to_position(next)); + if (is_null(offset)) + return {}; + + // element key matches (found) + if (keys::compare(unsafe_array_cast( + std::next(offset, Link::size)), key)) + return next; + + // set next element link (loop) + next = unsafe_array_cast(offset); + } + + return next; +} + +// static +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::read(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // Stream starts at record and the index is skipped for reader convenience. + iostream stream{ offset, size - position }; + reader source{ stream }; + source.skip_bytes(index_size); + + if constexpr (!is_slab) { BC_DEBUG_ONLY(source.set_limit(RowSize * element.count());) } + return element.from_data(source); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::write(const memory& ptr, const Link& link, const Key& key, + const Element& element) NOEXCEPT +{ + Link unused{}; + return write(unused, ptr, link, key, element); +} + +TEMPLATE +ELEMENT_CONSTRAINT +bool CLASS::write(Link& previous, const memory& ptr, const Link& link, + const Key& key, const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + // iostream.flush is a nop (direct copy). + iostream stream{ offset, size - position }; + finalizer sink{ stream }; + sink.skip_bytes(Link::size); + keys::write(sink, key); + + // Commit element to body. + if constexpr (!is_slab) { BC_DEBUG_ONLY(sink.set_limit(RowSize * element.count());) } + auto& next = unsafe_array_cast(offset); + if (!element.to_data(sink)) + return false; + + // Commit element to search (terminal is a valid bucket index). + bool search{}; + if (!head_.push(search, link, next, key)) + return false; + + // If collision set previous stack head for conflict resolution search. + previous = search ? Link{ next } : Link{}; + + // Report element write completion (count matches its allocation). + body_.complete(link, element.count()); + return true; +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/primitives/nomap.ipp b/include/bitcoin/database/impl/primitives/nomap.ipp index 31a5ba17a..d2a53d16a 100644 --- a/include/bitcoin/database/impl/primitives/nomap.ipp +++ b/include/bitcoin/database/impl/primitives/nomap.ipp @@ -1,311 +1,324 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP - -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::nomap(storage& header, storage& body) NOEXCEPT - : head_(header, 0), body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::backup(bool) NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && count == body_.count(); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::buckets() const NOEXCEPT -{ - return head_.buckets(); -} - -TEMPLATE -size_t CLASS::head_size() const NOEXCEPT -{ - return head_.size(); -} - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -size_t CLASS::capacity() const NOEXCEPT -{ - return body_.capacity(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -TEMPLATE -bool CLASS::truncate(const Link& count) NOEXCEPT -{ - return body_.truncate(count); -} - -TEMPLATE -bool CLASS::expand(const Link& count) NOEXCEPT -{ - return body_.expand(count); -} - -TEMPLATE -bool CLASS::drop() NOEXCEPT -{ - return body_.truncate(0) && backup(); -} - -TEMPLATE -bool CLASS::reserve(const Link& size) NOEXCEPT -{ - // Not writer-writer thread safe (two writers may share reserve). - return body_.reserve(size); -} - -TEMPLATE -Link CLASS::allocate(const Link& size) NOEXCEPT -{ - return body_.allocate(size); -} - -TEMPLATE -memory CLASS::get_memory() const NOEXCEPT -{ - return body_.get(); -} - -// error condition -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return body_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -// static -TEMPLATE -template > -bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - iostream stream{ offset, size - position }; - reader source{ stream }; - - if constexpr (!is_slab) - { - BC_DEBUG_ONLY(source.set_limit(Size * element.count());) - } - - return element.from_data(source); -} - -TEMPLATE -template > -inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - return get(get_memory(), link, element); -} - -TEMPLATE -template > -inline bool CLASS::put(const Element& element) NOEXCEPT -{ - Link link{}; - return put_link(link, element); -} - -TEMPLATE -template > -bool CLASS::put(const Link& link, const Element& element) NOEXCEPT -{ - using namespace system; - const auto ptr = body_.get(link); - return put(ptr, element); -} - -TEMPLATE -template > -bool CLASS::put(const memory& ptr, const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr) - return false; - - iostream stream{ ptr }; - flipper sink{ stream }; - - if constexpr (!is_slab) - { - BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) - } - - return element.to_data(sink); -} - -TEMPLATE -template > -bool CLASS::put(const memory& ptr, const Link& link, - const Element& element) NOEXCEPT -{ - using namespace system; - if (!ptr || link.is_terminal()) - return false; - - const auto start = body::link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - iostream stream{ offset, size - position }; - flipper sink{ stream }; - - if constexpr (!is_slab) - { - BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) - } - - return element.to_data(sink); -} - -TEMPLATE -template > -inline bool CLASS::put_link(Link& link, const Element& element) NOEXCEPT -{ - const auto count = element.count(); - link = body_.allocate(count); - return put(link, element); -} - -TEMPLATE -template > -inline Link CLASS::put_link(const Element& element) NOEXCEPT -{ - Link link{}; - return put_link(link, element) ? link : Link{}; -} - -// NOT THREAD SAFE (used only for height index with writer ordering). -TEMPLATE -template > -inline bool CLASS::commit(const Element& element) NOEXCEPT -{ - // Zero allocation provides link of next (presumably reserved) element. - const auto link = body_.allocate(0); - - // Write element into reserved but unallocated space. - if (!put(body_.get_capacity(link), element)) - return false; - - // Allocate reserved and written element (exposes logically). - return !body_.allocate(element.count()).is_terminal(); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAP_IPP + +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::nomap(storage& header, storage& body) NOEXCEPT + : head_(header, 0), body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && count == body_.count(); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::buckets() const NOEXCEPT +{ + return head_.buckets(); +} + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +size_t CLASS::capacity() const NOEXCEPT +{ + return body_.capacity(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + return body_.truncate(count); +} + +TEMPLATE +bool CLASS::expand(const Link& count) NOEXCEPT +{ + return body_.expand(count); +} + +TEMPLATE +bool CLASS::drop() NOEXCEPT +{ + return body_.truncate(0) && backup(); +} + +TEMPLATE +bool CLASS::reserve(const Link& size) NOEXCEPT +{ + // Not writer-writer thread safe (two writers may share reserve). + return body_.reserve(size); +} + +TEMPLATE +Link CLASS::allocate(const Link& size) NOEXCEPT +{ + return body_.allocate(size); +} + +TEMPLATE +memory CLASS::get_memory() const NOEXCEPT +{ + return body_.get(); +} + +// error condition +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return body_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +// static +TEMPLATE +template > +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + reader source{ stream }; + + if constexpr (!is_slab) + { + BC_DEBUG_ONLY(source.set_limit(Size * element.count());) + } + + return element.from_data(source); +} + +TEMPLATE +template > +inline bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + return get(get_memory(), link, element); +} + +TEMPLATE +template > +inline bool CLASS::put(const Element& element) NOEXCEPT +{ + Link link{}; + return put_link(link, element); +} + +TEMPLATE +template > +bool CLASS::put(const Link& link, const Element& element) NOEXCEPT +{ + using namespace system; + const auto ptr = body_.get(link); + if (!put(ptr, element)) + return false; + + body_.complete(link, element.count()); + return true; +} + +TEMPLATE +template > +bool CLASS::put(const memory& ptr, const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr) + return false; + + iostream stream{ ptr }; + flipper sink{ stream }; + + if constexpr (!is_slab) + { + BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) + } + + return element.to_data(sink); +} + +TEMPLATE +template > +bool CLASS::put(const memory& ptr, const Link& link, + const Element& element) NOEXCEPT +{ + using namespace system; + if (!ptr || link.is_terminal()) + return false; + + const auto start = body::link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + flipper sink{ stream }; + + if constexpr (!is_slab) + { + BC_DEBUG_ONLY(sink.set_limit(Size * element.count());) + } + + if (!element.to_data(sink)) + return false; + + body_.complete(link, element.count()); + return true; +} + +TEMPLATE +template > +inline bool CLASS::put_link(Link& link, const Element& element) NOEXCEPT +{ + const auto count = element.count(); + link = body_.allocate(count); + return put(link, element); +} + +TEMPLATE +template > +inline Link CLASS::put_link(const Element& element) NOEXCEPT +{ + Link link{}; + return put_link(link, element) ? link : Link{}; +} + +// NOT THREAD SAFE (used only for height index with writer ordering). +TEMPLATE +template > +inline bool CLASS::commit(const Element& element) NOEXCEPT +{ + // Zero allocation provides link of next (presumably reserved) element. + const auto link = body_.allocate(0); + + // Write element into reserved but unallocated space. + if (!put(body_.get_capacity(link), element)) + return false; + + // Allocate reserved and written element (exposes logically). + if (body_.allocate(element.count()).is_terminal()) + return false; + + // Publication of the pre-written element is its completion. + body_.complete(link, element.count()); + return true; +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/primitives/nomaps.ipp b/include/bitcoin/database/impl/primitives/nomaps.ipp index c896ff9c3..ff89f5a93 100644 --- a/include/bitcoin/database/impl/primitives/nomaps.ipp +++ b/include/bitcoin/database/impl/primitives/nomaps.ipp @@ -1,212 +1,216 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP - -#include -#include - -namespace libbitcoin { -namespace database { - -TEMPLATE -CLASS::nomaps(storage& header, storage& body) NOEXCEPT - : head_(header, 0), - body_(body) -{ -} - -// not thread safe -// ---------------------------------------------------------------------------- - -TEMPLATE -bool CLASS::create() NOEXCEPT -{ - Link count{}; - return head_.create() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::close() NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::backup(bool) NOEXCEPT -{ - return head_.set_body_count(body_.count()); -} - -TEMPLATE -bool CLASS::restore() NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && body_.truncate(count); -} - -TEMPLATE -bool CLASS::verify() const NOEXCEPT -{ - Link count{}; - return head_.verify() && - head_.get_body_count(count) && count == body_.count(); -} - -// sizing -// ---------------------------------------------------------------------------- - -TEMPLATE -size_t CLASS::body_size() const NOEXCEPT -{ - return body_.size(); -} - -TEMPLATE -Link CLASS::count() const NOEXCEPT -{ - return body_.count(); -} - -TEMPLATE -Link CLASS::allocate(const Link& count) NOEXCEPT -{ - return body_.allocate(count); -} - -TEMPLATE -bool CLASS::truncate(const Link& count) NOEXCEPT -{ - return body_.truncate(count); -} - -TEMPLATE -bool CLASS::drop() NOEXCEPT -{ - return body_.truncate(0) && backup(); -} - -// Faults. -// ---------------------------------------------------------------------------- - -TEMPLATE -code CLASS::get_fault() const NOEXCEPT -{ - return body_.get_fault(); -} - -TEMPLATE -size_t CLASS::get_space() const NOEXCEPT -{ - return body_.get_space(); -} - -TEMPLATE -code CLASS::reload() NOEXCEPT -{ - return body_.reload(); -} - -// query interface -// ---------------------------------------------------------------------------- - -TEMPLATE -memory CLASS::guard() const NOEXCEPT -{ - return get_memory(); -} - -TEMPLATE -template -memory CLASS::get_memory() const NOEXCEPT -{ - return body_.template get(); -} - -// static -TEMPLATE -template -bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT -{ - static_assert(Element::size == width); - if (!ptr || link.is_terminal()) - return false; - - using namespace system; - const auto start = body::template link_to_position(link); - if (is_limited(start)) - return false; - - const auto size = ptr.size(); - const auto position = possible_narrow_and_sign_cast(start); - if (position >= size) - return false; - - const auto offset = ptr.offset(start); - if (is_null(offset)) - return false; - - iostream stream{ offset, size - position }; - reader source{ stream }; - - BC_DEBUG_ONLY(source.set_limit(width * element.count());) - return element.from_data(source); -} - -// TODO: gets are not optimized for shared remap guard. -TEMPLATE -template -bool CLASS::get(const Link& link, Element& element) const NOEXCEPT -{ - return get(get_memory(), link, element); -} - -// TODO: puts are optimized for shared remap guard (required). -TEMPLATE -template -bool CLASS::put(const Link& link, const Element& element) NOEXCEPT -{ - const auto ptr = body_.template get_raw1(link); - return put(ptr, element); -} - -// protected (unguarded memory access) -TEMPLATE -template -bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT -{ - static_assert(Element::size == width); - if (is_null(it)) - return false; - - using namespace system; - const auto bytes = width * element.count(); - iostream stream{ it, possible_narrow_and_sign_cast(bytes) }; - flipper sink{ stream }; - - BC_DEBUG_ONLY(sink.set_limit(width * element.count());) - return element.to_data(sink); -} - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_NOMAPS_IPP + +#include +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::nomaps(storage& header, storage& body) NOEXCEPT + : head_(header, 0), + body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && count == body_.count(); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +Link CLASS::allocate(const Link& count) NOEXCEPT +{ + return body_.allocate(count); +} + +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + return body_.truncate(count); +} + +TEMPLATE +bool CLASS::drop() NOEXCEPT +{ + return body_.truncate(0) && backup(); +} + +// Faults. +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + return body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return body_.get_space(); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface +// ---------------------------------------------------------------------------- + +TEMPLATE +memory CLASS::guard() const NOEXCEPT +{ + return get_memory(); +} + +TEMPLATE +template +memory CLASS::get_memory() const NOEXCEPT +{ + return body_.template get(); +} + +// static +TEMPLATE +template +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = body::template link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_and_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + reader source{ stream }; + + BC_DEBUG_ONLY(source.set_limit(width * element.count());) + return element.from_data(source); +} + +// TODO: gets are not optimized for shared remap guard. +TEMPLATE +template +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + return get(get_memory(), link, element); +} + +// TODO: puts are optimized for shared remap guard (required). +TEMPLATE +template +bool CLASS::put(const Link& link, const Element& element) NOEXCEPT +{ + const auto ptr = body_.template get_raw1(link); + if (!put(ptr, element)) + return false; + + body_.complete(link, element.count()); + return true; +} + +// protected (unguarded memory access) +TEMPLATE +template +bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (is_null(it)) + return false; + + using namespace system; + const auto bytes = width * element.count(); + iostream stream{ it, possible_narrow_and_sign_cast(bytes) }; + flipper sink{ stream }; + + BC_DEBUG_ONLY(sink.set_limit(width * element.count());) + return element.to_data(sink); +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/store/store.ipp b/include/bitcoin/database/impl/store/store.ipp index 29f589649..7f9b35a28 100644 --- a/include/bitcoin/database/impl/store/store.ipp +++ b/include/bitcoin/database/impl/store/store.ipp @@ -32,78 +32,78 @@ CLASS::store(const settings& config) NOEXCEPT // ------------------------------------------------------------------------ header_head_(head(config.path / schema::dir::heads, schema::archive::header), 1, 0, random), - header_body_(body(config.path, schema::archive::header), config.header_size, config.header_rate, sequential), + header_body_(body(config.path, schema::archive::header), config.header_size, config.header_rate, sequential, staged), input_head_(head(config.path / schema::dir::heads, schema::archive::input), 1, 0, sequential), - input_body_(body(config.path, schema::archive::input), config.input_size, config.input_rate, sequential), + input_body_(body(config.path, schema::archive::input), config.input_size, config.input_rate, sequential, staged), output_head_(head(config.path / schema::dir::heads, schema::archive::output), 1, 0, sequential), - output_body_(body(config.path, schema::archive::output), config.output_size, config.output_rate, sequential), + output_body_(body(config.path, schema::archive::output), config.output_size, config.output_rate, sequential, staged), ins_head_(head(config.path / schema::dir::heads, schema::archive::ins), 1, 0, random), - ins_body_(body(config.path, schema::archive::ins), config.ins_size, config.ins_rate, sequential), + ins_body_(body(config.path, schema::archive::ins), config.ins_size, config.ins_rate, sequential, staged), outs_head_(head(config.path / schema::dir::heads, schema::archive::outs), 1, 0, sequential), - outs_body_(body(config.path, schema::archive::outs), config.outs_size, config.outs_rate, sequential), + outs_body_(body(config.path, schema::archive::outs), config.outs_size, config.outs_rate, sequential, staged), tx_head_(head(config.path / schema::dir::heads, schema::archive::tx), 1, 0, random), - tx_body_(body(config.path, schema::archive::tx), config.tx_size, config.tx_rate, sequential), + tx_body_(body(config.path, schema::archive::tx), config.tx_size, config.tx_rate, sequential, staged), txs_head_(head(config.path / schema::dir::heads, schema::archive::txs), 1, 0, random), - txs_body_(body(config.path, schema::archive::txs), config.txs_size, config.txs_rate, sequential), + txs_body_(body(config.path, schema::archive::txs), config.txs_size, config.txs_rate, sequential, staged), // Indexes. // ------------------------------------------------------------------------ candidate_head_(head(config.path / schema::dir::heads, schema::indexes::candidate), 1, 0, sequential), - candidate_body_(body(config.path, schema::indexes::candidate), config.candidate_size, config.candidate_rate, sequential), + candidate_body_(body(config.path, schema::indexes::candidate), config.candidate_size, config.candidate_rate, sequential, staged), confirmed_head_(head(config.path / schema::dir::heads, schema::indexes::confirmed), 1, 0, sequential), - confirmed_body_(body(config.path, schema::indexes::confirmed), config.confirmed_size, config.confirmed_rate, sequential), + confirmed_body_(body(config.path, schema::indexes::confirmed), config.confirmed_size, config.confirmed_rate, sequential, staged), strong_tx_head_(head(config.path / schema::dir::heads, schema::indexes::strong_tx), 1, 0, random), - strong_tx_body_(body(config.path, schema::indexes::strong_tx), config.strong_tx_size, config.strong_tx_rate, sequential), + strong_tx_body_(body(config.path, schema::indexes::strong_tx), config.strong_tx_size, config.strong_tx_rate, sequential, staged), // Caches. // ------------------------------------------------------------------------ // TODO: body not random, but keep in memory. ecdsa_head_(head(config.path / schema::dir::heads, schema::caches::ecdsa), 1, 0, sequential), - ecdsa_body_(body(config.path, schema::caches::ecdsa), config.ecdsa_size, config.ecdsa_rate, sequential), + ecdsa_body_(body(config.path, schema::caches::ecdsa), config.ecdsa_size, config.ecdsa_rate, sequential, staged), // TODO: body not random, but keep in memory. schnorr_head_(head(config.path / schema::dir::heads, schema::caches::schnorr), 1, 0, sequential), - schnorr_body_(body(config.path, schema::caches::schnorr), config.schnorr_size, config.schnorr_rate, sequential), + schnorr_body_(body(config.path, schema::caches::schnorr), config.schnorr_size, config.schnorr_rate, sequential, staged), silent_head_(head(config.path / schema::dir::heads, schema::caches::silent), 1, 0, sequential), - silent_body_(body(config.path, schema::caches::silent), config.silent_size, config.silent_rate, sequential), + silent_body_(body(config.path, schema::caches::silent), config.silent_size, config.silent_rate, sequential, staged), duplicate_head_(head(config.path / schema::dir::heads, schema::caches::duplicate), 1, 0, random), - duplicate_body_(body(config.path, schema::caches::duplicate), config.duplicate_size, config.duplicate_rate, sequential), + duplicate_body_(body(config.path, schema::caches::duplicate), config.duplicate_size, config.duplicate_rate, sequential, staged), prevalid_head_(head(config.path / schema::dir::heads, schema::caches::prevalid), 1, 0, sequential), - prevalid_body_(body(config.path, schema::caches::prevalid), config.prevalid_size, config.prevalid_rate, sequential), + prevalid_body_(body(config.path, schema::caches::prevalid), config.prevalid_size, config.prevalid_rate, sequential, staged), prevout_head_(head(config.path / schema::dir::heads, schema::caches::prevout), 1, 0, random), - prevout_body_(body(config.path, schema::caches::prevout), config.prevout_size, config.prevout_rate, sequential), + prevout_body_(body(config.path, schema::caches::prevout), config.prevout_size, config.prevout_rate, sequential, staged), validated_bk_head_(head(config.path / schema::dir::heads, schema::caches::validated_bk), 1, 0, random), - validated_bk_body_(body(config.path, schema::caches::validated_bk), config.validated_bk_size, config.validated_bk_rate, sequential), + validated_bk_body_(body(config.path, schema::caches::validated_bk), config.validated_bk_size, config.validated_bk_rate, sequential, staged), validated_tx_head_(head(config.path / schema::dir::heads, schema::caches::validated_tx), 1, 0, random), - validated_tx_body_(body(config.path, schema::caches::validated_tx), config.validated_tx_size, config.validated_tx_rate, sequential), + validated_tx_body_(body(config.path, schema::caches::validated_tx), config.validated_tx_size, config.validated_tx_rate, sequential, staged), // Optionals. // ------------------------------------------------------------------------ address_head_(head(config.path / schema::dir::heads, schema::optionals::address), 1, 0, random), - address_body_(body(config.path, schema::optionals::address), config.address_size, config.address_rate, sequential), + address_body_(body(config.path, schema::optionals::address), config.address_size, config.address_rate, sequential, staged), filter_bk_head_(head(config.path / schema::dir::heads, schema::optionals::filter_bk), 1, 0, random), - filter_bk_body_(body(config.path, schema::optionals::filter_bk), config.filter_bk_size, config.filter_bk_rate, sequential), + filter_bk_body_(body(config.path, schema::optionals::filter_bk), config.filter_bk_size, config.filter_bk_rate, sequential, staged), filter_tx_head_(head(config.path / schema::dir::heads, schema::optionals::filter_tx), 1, 0, random), - filter_tx_body_(body(config.path, schema::optionals::filter_tx), config.filter_tx_size, config.filter_tx_rate, sequential), + filter_tx_body_(body(config.path, schema::optionals::filter_tx), config.filter_tx_size, config.filter_tx_rate, sequential, staged), // Locks. // ------------------------------------------------------------------------ diff --git a/include/bitcoin/database/impl/store/store_reload.ipp b/include/bitcoin/database/impl/store/store_reload.ipp index 35f727ee7..57bcc1ec9 100644 --- a/include/bitcoin/database/impl/store/store_reload.ipp +++ b/include/bitcoin/database/impl/store/store_reload.ipp @@ -40,13 +40,16 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT { if (!ec) { - // If any storage has a fault it will return as failure code. if (to_bool(file.get_space())) { handler(event_t::load_file, table); - ec = file.reload(); this->dirty_.store(true, std::memory_order_relaxed); } + + // Reload all storages, as staged instances must discard extents + // orphaned by abandoned writes (which may span into tables that + // did not themselves fault). Returns any storage fault as failure. + ec = file.reload(); } }; diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index ad0cdd1e3..4ff45a869 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -1,121 +1,127 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_MEMORY_INTERFACES_STORAGE_HPP -#define LIBBITCOIN_DATABASE_MEMORY_INTERFACES_STORAGE_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace database { - -/// Mapped memory interface. -/// A slab has a row width of 1, so "count" implies "bytes" for slabs below. -class storage -{ -public: - static constexpr auto eof = system::bit_all; - using path = std::filesystem::path; - - /// Get the fault condition. - virtual code get_fault() const NOEXCEPT = 0; - - /// Get the space required to clear the disk full condition. - virtual size_t get_space() const NOEXCEPT = 0; - - /// The filesystem path of the backing storage. - virtual const path& file() const NOEXCEPT = 0; - - /// Create empty file, must not exist. - virtual code create() const NOEXCEPT = 0; - - /// Open file, must be closed. - virtual code open() NOEXCEPT = 0; - - /// Close file, must be unloaded, idempotent. - virtual code close() NOEXCEPT = 0; - - /// Map file to memory, must be open and unloaded. - virtual code load() NOEXCEPT = 0; - - /// Clear disk full condition, fails if fault, must be loaded, idempotent. - virtual code reload() NOEXCEPT = 0; - - /// Flush memory map to disk, suspend writes for call, must be loaded. - virtual code flush() NOEXCEPT = 0; - - /// Flush, unmap and truncate to logical, restartable, idempotent. - virtual code unload() NOEXCEPT = 0; - - /// Unload and load, causing underyling map to shrink to logical size. - virtual code shrink() NOEXCEPT = 0; - - /// Dump current logical map to a new file in path, must not exist. - virtual code dump(const path& path) const NOEXCEPT = 0; - - /// Current of rows/bytes in map (zero if closed). - virtual size_t size() const NOEXCEPT = 0; - - /// The current count of rows/bytes in map (zero if closed). - virtual size_t capacity() const NOEXCEPT = 0; - - /// Reduce logical size to specified rows/bytes (false if exceeds logical). - virtual bool truncate(size_t count) NOEXCEPT = 0; - - /// Increase logical to specified rows/bytes as required (false if fails). - virtual bool expand(size_t count) NOEXCEPT = 0; - - /// Increase capacity by specified rows/bytes (false only if fails). - virtual bool reserve(size_t count) NOEXCEPT = 0; - - /// Increase logical by specified rows/bytes, return row of first (or eof). - virtual size_t allocate(size_t count) NOEXCEPT = 0; - - /// Get remap-protected r/w access to offset (or null) allocated to size. - virtual memory get_filled(size_t offset, size_t size, - uint8_t backfill) NOEXCEPT = 0; - - /// Get remap-protected r/w access to start/offset of memory map (or null). - /// Pointer is constrained to starting write within full capacity. - virtual memory get_capacity(size_t offset=zero) const NOEXCEPT = 0; - - /// Get unprotected r/w access to start/offset of memory map (or null). - /// Pointer is constrained to starting write within full capacity. - virtual memory::iterator get_raw(size_t offset=zero) const NOEXCEPT = 0; - - /// Get unprotected r/w access to start/offset of memory map (or null). - /// Pointer is constrained to starting write within full capacity. - virtual memory::iterator get_raw_at(size_t column, - size_t offset=zero) const NOEXCEPT = 0; - - /// Get remap-protected r/w access to start/offset of memory map (or null). - /// Pointer is constrained to starting write within logical allocation. - virtual memory get(size_t offset=zero) const NOEXCEPT = 0; - - /// Same as get() but within specified column (or null for invalid column). - /// Pointer is constrained to starting write within logical allocation. - virtual memory get_at(size_t column, - size_t offset=zero) const NOEXCEPT = 0; -}; - -} // namespace database -} // namespace libbitcoin - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_MEMORY_INTERFACES_STORAGE_HPP +#define LIBBITCOIN_DATABASE_MEMORY_INTERFACES_STORAGE_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace database { + +/// Mapped memory interface. +/// A slab has a row width of 1, so "count" implies "bytes" for slabs below. +class storage +{ +public: + static constexpr auto eof = system::bit_all; + using path = std::filesystem::path; + + /// Get the fault condition. + virtual code get_fault() const NOEXCEPT = 0; + + /// Get the space required to clear the disk full condition. + virtual size_t get_space() const NOEXCEPT = 0; + + /// The filesystem path of the backing storage. + virtual const path& file() const NOEXCEPT = 0; + + /// Create empty file, must not exist. + virtual code create() const NOEXCEPT = 0; + + /// Open file, must be closed. + virtual code open() NOEXCEPT = 0; + + /// Close file, must be unloaded, idempotent. + virtual code close() NOEXCEPT = 0; + + /// Map file to memory, must be open and unloaded. + virtual code load() NOEXCEPT = 0; + + /// Clear disk full condition, fails if fault, must be loaded, idempotent. + virtual code reload() NOEXCEPT = 0; + + /// Flush memory map to disk, suspend writes for call, must be loaded. + virtual code flush() NOEXCEPT = 0; + + /// Flush, unmap and truncate to logical, restartable, idempotent. + virtual code unload() NOEXCEPT = 0; + + /// Unload and load, causing underyling map to shrink to logical size. + virtual code shrink() NOEXCEPT = 0; + + /// Dump current logical map to a new file in path, must not exist. + virtual code dump(const path& path) const NOEXCEPT = 0; + + /// Current of rows/bytes in map (zero if closed). + virtual size_t size() const NOEXCEPT = 0; + + /// The current count of rows/bytes in map (zero if closed). + virtual size_t capacity() const NOEXCEPT = 0; + + /// Reduce logical size to specified rows/bytes (false if exceeds logical). + virtual bool truncate(size_t count) NOEXCEPT = 0; + + /// Increase logical to specified rows/bytes as required (false if fails). + virtual bool expand(size_t count) NOEXCEPT = 0; + + /// Increase capacity by specified rows/bytes (false only if fails). + virtual bool reserve(size_t count) NOEXCEPT = 0; + + /// Increase logical by specified rows/bytes, return row of first (or eof). + virtual size_t allocate(size_t count) NOEXCEPT = 0; + + /// Report element write completion of count rows/bytes at offset. + virtual void complete(size_t offset, size_t count) NOEXCEPT = 0; + + /// Rows/bytes below which all writes are complete (size() when quiescent). + virtual size_t frontier() const NOEXCEPT = 0; + + /// Get remap-protected r/w access to offset (or null) allocated to size. + virtual memory get_filled(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT = 0; + + /// Get remap-protected r/w access to start/offset of memory map (or null). + /// Pointer is constrained to starting write within full capacity. + virtual memory get_capacity(size_t offset=zero) const NOEXCEPT = 0; + + /// Get unprotected r/w access to start/offset of memory map (or null). + /// Pointer is constrained to starting write within full capacity. + virtual memory::iterator get_raw(size_t offset=zero) const NOEXCEPT = 0; + + /// Get unprotected r/w access to start/offset of memory map (or null). + /// Pointer is constrained to starting write within full capacity. + virtual memory::iterator get_raw_at(size_t column, + size_t offset=zero) const NOEXCEPT = 0; + + /// Get remap-protected r/w access to start/offset of memory map (or null). + /// Pointer is constrained to starting write within logical allocation. + virtual memory get(size_t offset=zero) const NOEXCEPT = 0; + + /// Same as get() but within specified column (or null for invalid column). + /// Pointer is constrained to starting write within logical allocation. + virtual memory get_at(size_t column, + size_t offset=zero) const NOEXCEPT = 0; +}; + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/memory/memory.hpp b/include/bitcoin/database/memory/memory.hpp index 074854c88..b60ba4102 100644 --- a/include/bitcoin/database/memory/memory.hpp +++ b/include/bitcoin/database/memory/memory.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #endif diff --git a/include/bitcoin/database/memory/mman.hpp b/include/bitcoin/database/memory/mman.hpp index cde0497df..ea2b1766c 100644 --- a/include/bitcoin/database/memory/mman.hpp +++ b/include/bitcoin/database/memory/mman.hpp @@ -1,61 +1,61 @@ -// mman_win32 based on code.google.com/p/mman-win32 (MIT License). - -#ifndef LIBBITCOIN_DATABASE_MMAN_HPP -#define LIBBITCOIN_DATABASE_MMAN_HPP - -#include - -#if !defined(HAVE_MSC) - #include - #include - #include - #include -#endif - -#if defined(HAVE_MSC) - -typedef size_t oft__; - -#define PROT_NONE 0 -#define PROT_READ 1 -#define PROT_WRITE 2 -#define PROT_EXEC 4 - -#define MAP_FILE 0 -#define MAP_SYNC 0 -#define MAP_SHARED 1 -#define MAP_SHARED_VALIDATE MAP_SHARED -#define MAP_PRIVATE 2 -#define MAP_TYPE 0xf -#define MAP_FIXED 0x10 -#define MAP_ANONYMOUS 0x20 -#define MAP_ANON MAP_ANONYMOUS - -#define MAP_FAILED ((void*)-1) - -// Flags for msync. -#define MS_ASYNC 1 -#define MS_SYNC 2 -#define MS_INVALIDATE 4 - -void* mmap(void* addr, size_t len, int prot, int flags, int fd, - oft__ off) noexcept; -void* mremap_(void* addr, size_t old_size, size_t new_size, int prot, - int flags, int fd) noexcept; -int munmap(void* addr, size_t len) noexcept; -int madvise(void* addr, size_t len, int advice) noexcept; -int mprotect(void* addr, size_t len, int prot) noexcept; -int msync(void* addr, size_t len, int flags) noexcept; -int mlock(const void* addr, size_t len) noexcept; -int munlock(const void* addr, size_t len) noexcept; -int fsync(int fd) noexcept; -int fallocate(int fd, int mode, oft__ offset, oft__ size) noexcept; -int ftruncate(int fd, oft__ size) noexcept; - -#elif defined(HAVE_APPLE) - -int fallocate(int fd, int, off_t offset, off_t len) NOEXCEPT; - -#endif // HAVE_MSC - -#endif +// mman_win32 based on code.google.com/p/mman-win32 (MIT License). + +#ifndef LIBBITCOIN_DATABASE_MMAN_HPP +#define LIBBITCOIN_DATABASE_MMAN_HPP + +#include + +#if !defined(HAVE_MSC) + #include + #include + #include + #include +#endif + +#if defined(HAVE_MSC) + +typedef size_t oft__; + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FILE 0 +#define MAP_SYNC 0 +#define MAP_SHARED 1 +#define MAP_SHARED_VALIDATE MAP_SHARED +#define MAP_PRIVATE 2 +#define MAP_TYPE 0xf +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void*)-1) + +// Flags for msync. +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void* addr, size_t len, int prot, int flags, int fd, + oft__ off) noexcept; +void* mremap_(void* addr, size_t old_size, size_t new_size, int prot, + int flags, int fd) noexcept; +int munmap(void* addr, size_t len) noexcept; +int madvise(void* addr, size_t len, int advice) noexcept; +int mprotect(void* addr, size_t len, int prot) noexcept; +int msync(void* addr, size_t len, int flags) noexcept; +int mlock(const void* addr, size_t len) noexcept; +int munlock(const void* addr, size_t len) noexcept; +int fsync(int fd) noexcept; +int fallocate(int fd, int mode, oft__ offset, oft__ size) noexcept; +int ftruncate(int fd, oft__ size) noexcept; + +#elif defined(HAVE_APPLE) + +int fallocate(int fd, int, off_t offset, off_t len) NOEXCEPT; + +#endif // HAVE_MSC + +#endif diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index f98a403b4..4ecafd11c 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -20,14 +20,17 @@ #define LIBBITCOIN_DATABASE_MEMORY_MMAP_HPP #include +#include #include #include #include +#include #include #include #include #include #include +#include namespace libbitcoin { namespace database { @@ -57,13 +60,22 @@ class mmap /// Constructors. /// ----------------------------------------------------------------------- + /// Staged instances (append-only bodies) stage writes in anonymous memory + /// and convert flushed rows to a read-only file mapping, hard-faulting any + /// write into settled space. Unstaged instances (heads) hold all logical + /// content in anonymous memory for the life of the load. Both write the + /// file only by explicit transfer, so no dirty file-backed page ever + /// exists (no effect where the staging backend is not built). + /// Scalar construction (columns == 1): unchanged signature and codegen. mmap(const path& filename, size_t minimum=1, size_t expansion=0, - bool random=true) NOEXCEPT requires (is_one(columns)); + bool random=true, bool staged=false) NOEXCEPT + requires (is_one(columns)); /// Aggregate construction (columns > 1): one file per column, shared guards. mmap(const paths& filenames, size_t minimum=1, size_t expansion=0, - bool random=true) NOEXCEPT requires (columns > one); + bool random=true, bool staged=false) NOEXCEPT + requires (columns > one); /// Destruct for debug assertion only. virtual ~mmap() NOEXCEPT; @@ -131,6 +143,12 @@ class mmap /// Increase logical by specified rows/bytes, return row of first (or eof). size_t allocate(size_t count) NOEXCEPT override; + /// Report element write completion of count rows/bytes at offset. + void complete(size_t offset, size_t count) NOEXCEPT override; + + /// Rows/bytes below which all writes are complete (size() when quiescent). + size_t frontier() const NOEXCEPT override; + /// Remap-protected r/w access to offset (or null) allocated to size. memory get_filled(size_t offset, size_t size, uint8_t backfill) NOEXCEPT override; @@ -180,7 +198,7 @@ class mmap // mman dispatch, not thread safe. template - bool flush_all_(std::index_sequence) NOEXCEPT; + bool flush_all_(size_t rows, std::index_sequence) NOEXCEPT; template bool map_all_(std::index_sequence) NOEXCEPT; template @@ -190,7 +208,7 @@ class mmap // mman wrappers, not thread safe. template - bool flush_() NOEXCEPT; + bool flush_(size_t rows) NOEXCEPT; template bool map_() NOEXCEPT; template @@ -204,31 +222,104 @@ class mmap template bool finalize_(size_t size) NOEXCEPT; - // These are thread safe. +#if defined(MANAGE_STAGING) + // staging dispatch, not thread safe. + template + bool settle_all_(size_t rows, std::index_sequence) NOEXCEPT; + template + bool unsettle_all_(size_t rows, std::index_sequence) NOEXCEPT; + + // staging wrappers, not thread safe. + template + bool stage_() NOEXCEPT; + template + bool commit_(size_t size) NOEXCEPT; + template + bool settle_(size_t from, size_t to) NOEXCEPT; + template + bool unsettle_(size_t rows) NOEXCEPT; + template + void teardown_(const error::error_t& ec) NOEXCEPT; + + // staging utilities, not thread safe. + void record_(size_t start, size_t count) NOEXCEPT; + void maintain_() NOEXCEPT; + void discard_() NOEXCEPT; + void throttle_() NOEXCEPT; + void signal_() NOEXCEPT; + + // settle scheduler (instance-owned thread, load/unload lifecycle). + void settler_start_() NOEXCEPT; + void settler_stop_() NOEXCEPT; + void settler_run_() NOEXCEPT; + bool settle_next_(size_t chunk) NOEXCEPT; + template + bool settle_write_(size_t from, size_t to, + std::index_sequence) NOEXCEPT; + bool advise_(uint8_t* map, size_t size) const NOEXCEPT; + size_t to_reservation(size_t rows) const NOEXCEPT; + size_t page_floor(size_t bytes) const NOEXCEPT; + size_t page_ceiling(size_t bytes) const NOEXCEPT; +#endif // MANAGE_STAGING + + // These are thread safe (const). const paths filenames_; const size_t minimum_; const size_t expansion_; const bool random_; - std::atomic space_{ zero }; - std::atomic error_{ error::success }; + const bool staged_; - // Scalar fields are atomic: size/capacity reads and the allocate fast - // path (bounded claim within published capacity) are lock-free. Capacity - // growth and compound transitions (open/close/load/unload/shrink/ - // truncate/expand/reserve/get_filled) are serialized by field_mutex_ - // exclusive. Shrinking transitions additionally rely on the documented - // suspend-writes contract (the fast path does not serialize with them). - // logical_ and capacity_ are row counts (byte count if width is one). - std::array opened_; + // These are thread safe (atomic). + std::atomic error_{ error::success }; + std::atomic space_{ zero }; std::atomic capacity_{}; std::atomic logical_{}; - std::atomic fault_{}; - std::atomic loaded_{}; + std::atomic_bool fault_{}; + std::atomic_bool loaded_{}; + + // This is protected by field_mutex_. + std::array opened_; mutable std::shared_mutex field_mutex_{}; - // These are protected by remap_mutex_. + // This is protected by remap_mutex_. std::array memory_map_{}; mutable std::shared_mutex remap_mutex_{}; + +#if defined(MANAGE_STAGING) + static constexpr size_t extents = 4096; + struct extent + { + std::atomic start; + size_t count; + std::atomic outstanding; + }; + + // These are thread safe (atomic). The ring window packs head and size + // into one word (pack_word) so lock-free readers cannot observe a torn + // window across pops. + std::atomic settled_{}; + std::atomic frontier_{}; + std::atomic window_{}; + + // These are protected by extent_mutex_ (ring entries are immobile, put + // under the mutex and published by release, read/decremented lock-free). + std::array reserved_{}; + std::array ring_{}; + size_t page_{}; + mutable std::mutex extent_mutex_{}; + + // These are protected by settler_mutex_ (thread joined by stop). + std::thread settler_{}; + std::condition_variable settler_cv_{}; + std::atomic_bool settling_{}; + mutable std::mutex settler_mutex_{}; + + // These delay allocation while staging exceeds its memory bound (write + // throttle). The bound is set at load, before writers exist. + size_t limit_{}; + std::condition_variable throttle_cv_{}; + mutable std::mutex throttle_mutex_{}; +#endif // MANAGE_STAGING }; using map = mmap; @@ -244,6 +335,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) #include #include #include +#include #include BC_POP_WARNING() diff --git a/include/bitcoin/database/memory/mmaps.hpp b/include/bitcoin/database/memory/mmaps.hpp index e8edfa90d..8f42eceb3 100644 --- a/include/bitcoin/database/memory/mmaps.hpp +++ b/include/bitcoin/database/memory/mmaps.hpp @@ -52,17 +52,17 @@ class mmaps }; mmaps(const path& base_path, size_t size, size_t rate, - bool random_access) NOEXCEPT - : mmaps(base_path, size, rate, random_access, sequence) + bool random_access, bool staged=false) NOEXCEPT + : mmaps(base_path, size, rate, random_access, staged, sequence) { } protected: template mmaps(const path& base_path, size_t size, size_t rate, bool random_access, - std::index_sequence) NOEXCEPT + bool staged, std::index_sequence) NOEXCEPT : base(paths{ to_subpath(base_path, suffixes[Index])... }, size, rate, - random_access), + random_access, staged), base_path_{ base_path } { } diff --git a/include/bitcoin/database/memory/mstage.hpp b/include/bitcoin/database/memory/mstage.hpp new file mode 100644 index 000000000..61c9f2d47 --- /dev/null +++ b/include/bitcoin/database/memory/mstage.hpp @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_MEMORY_MSTAGE_HPP +#define LIBBITCOIN_DATABASE_MEMORY_MSTAGE_HPP + +#include + +#if defined(HAVE_APPLE) + #define MANAGE_STAGING +#endif + +#if defined(MANAGE_STAGING) + +/// Reserve inaccessible anonymous address space (MAP_FAILED on failure). +void* mmap_reserve(size_t size) NOEXCEPT; + +/// Commit reserved pages as readable/writable anonymous memory. +int mmap_commit(void* address, size_t size) NOEXCEPT; + +/// Replace committed pages with a read-only shared mapping of the file. +int mmap_settle(void* address, size_t size, int fd, size_t offset) NOEXCEPT; + +/// Replace settled pages with committed anonymous memory (contents undefined). +int mmap_unsettle(void* address, size_t size) NOEXCEPT; + +/// Full-transfer positional file read/write (false on failure or early eof). +bool pread_all(int fd, uint8_t* to, size_t size, size_t offset) NOEXCEPT; +bool pwrite_all(int fd, const uint8_t* from, size_t size, + size_t offset) NOEXCEPT; + +#endif // MANAGE_STAGING + +#endif diff --git a/include/bitcoin/database/memory/utilities.hpp b/include/bitcoin/database/memory/utilities.hpp index a1bc60b81..90a9341ac 100644 --- a/include/bitcoin/database/memory/utilities.hpp +++ b/include/bitcoin/database/memory/utilities.hpp @@ -31,6 +31,10 @@ BCD_API size_t page_size() NOEXCEPT; /// The bytes of physical memory, zero if failed. BCD_API uint64_t system_memory() NOEXCEPT; +/// The system memory pressure level (1 normal, 2 warning, 4 critical), zero +/// if failed or the platform provides no source. +BCD_API size_t system_pressure() NOEXCEPT; + /// C++26: std::atomic::fetch_max template = true> Integral fetch_max(std::atomic& atomic, Integral value) NOEXCEPT diff --git a/include/bitcoin/database/primitives/body.hpp b/include/bitcoin/database/primitives/body.hpp index 0b2067156..430851681 100644 --- a/include/bitcoin/database/primitives/body.hpp +++ b/include/bitcoin/database/primitives/body.hpp @@ -1,133 +1,136 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_HPP -#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_HPP - -#include -#include -#include -#include - -namespace libbitcoin { -namespace database { - -template -class bodys -{ -public: - using integer = typename Link::integer; - - template - static constexpr Link position_to_link(size_t position) NOEXCEPT; - template - static constexpr size_t link_to_position(const Link& link) NOEXCEPT; - static constexpr integer cast_link(size_t link) NOEXCEPT; - -public: - DEFAULT_COPY_MOVE_DESTRUCT(bodys); - - /// Return memory object for column full map (null only if oom or unloaded). - template - inline memory get() const NOEXCEPT; - - /// Return memory object for column record (null only if oom or unloaded). - /// Pointer is constrained to starting write within logical allocation. - template - inline memory get(const Link& link) const NOEXCEPT; - template - inline memory::iterator get_raw1(const Link& link) const NOEXCEPT; - - /// Return memory object (limited to AoS) within capacity. - template = true> - inline memory get_capacity(const Link& link) const NOEXCEPT; - - /// Manage shared multi-backed byte storage device (caller owns storage). - bodys(storage& body) NOEXCEPT; - - /// The aggregate logical byte size (cold size) across all columns. - inline size_t size() const NOEXCEPT; - - /// The aggregate byte capacity (hot size) across all columns. - inline size_t capacity() const NOEXCEPT; - - /// The logical record count (common across columns). - inline Link count() const NOEXCEPT; - - /// Reduce logical size to count records (false if exceeds logical). - bool truncate(const Link& count) NOEXCEPT; - - /// Increase logical size to count records as required (false if fails). - bool expand(const Link& count) NOEXCEPT; - - /// Thread safe but reservations do not accumulate (effectively unsafe). - /// Increase capacity by count records (false only if fails). - bool reserve(const Link& count) NOEXCEPT; - - /// Unified allocation across all columns (one lock). - /// Increase logical size by count records, return offset to first (or eof). - Link allocate(const Link& count) NOEXCEPT; - - /// Get the unified fault condition. - code get_fault() const NOEXCEPT; - - /// Get the unified space required to clear the disk full condition. - size_t get_space() const NOEXCEPT; - - /// Unified resume from disk full condition. - code reload() NOEXCEPT; - -private: - static constexpr auto columns = sizeof...(Sizes); - static constexpr auto key_size = keys::size(); - static constexpr std::array sizes{ Sizes... }; - static constexpr auto is_slab = (std::get(sizes) == max_size_t); - static_assert(!is_slab || is_one(columns), "slab implies single column"); - static_assert(!is_zero(columns), "requires at least one column"); - - template - static constexpr size_t stride() NOEXCEPT; - template - static constexpr size_t strides(std::index_sequence) NOEXCEPT; - - /// Convert between record links and the file's native denomination - /// (elements). Single column file elements are BYTES (width one), so - /// records convert by stride (slabs pass through, links are byte offsets). - /// Aggregate file elements are ROWS, which equal records (pass through). - static constexpr size_t link_to_elements(const Link& link) NOEXCEPT; - static constexpr Link elements_to_link(size_t elements) NOEXCEPT; - - // Thread and remap safe. - storage& files_; -}; - -template -using body = bodys; - -} // namespace database -} // namespace libbitcoin - -#define TEMPLATE template -#define CLASS bodys - -#include - -#undef CLASS -#undef TEMPLATE - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_BODY_HPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_BODY_HPP + +#include +#include +#include +#include + +namespace libbitcoin { +namespace database { + +template +class bodys +{ +public: + using integer = typename Link::integer; + + template + static constexpr Link position_to_link(size_t position) NOEXCEPT; + template + static constexpr size_t link_to_position(const Link& link) NOEXCEPT; + static constexpr integer cast_link(size_t link) NOEXCEPT; + +public: + DEFAULT_COPY_MOVE_DESTRUCT(bodys); + + /// Return memory object for column full map (null only if oom or unloaded). + template + inline memory get() const NOEXCEPT; + + /// Return memory object for column record (null only if oom or unloaded). + /// Pointer is constrained to starting write within logical allocation. + template + inline memory get(const Link& link) const NOEXCEPT; + template + inline memory::iterator get_raw1(const Link& link) const NOEXCEPT; + + /// Return memory object (limited to AoS) within capacity. + template = true> + inline memory get_capacity(const Link& link) const NOEXCEPT; + + /// Manage shared multi-backed byte storage device (caller owns storage). + bodys(storage& body) NOEXCEPT; + + /// The aggregate logical byte size (cold size) across all columns. + inline size_t size() const NOEXCEPT; + + /// The aggregate byte capacity (hot size) across all columns. + inline size_t capacity() const NOEXCEPT; + + /// The logical record count (common across columns). + inline Link count() const NOEXCEPT; + + /// Reduce logical size to count records (false if exceeds logical). + bool truncate(const Link& count) NOEXCEPT; + + /// Increase logical size to count records as required (false if fails). + bool expand(const Link& count) NOEXCEPT; + + /// Thread safe but reservations do not accumulate (effectively unsafe). + /// Increase capacity by count records (false only if fails). + bool reserve(const Link& count) NOEXCEPT; + + /// Unified allocation across all columns (one lock). + /// Increase logical size by count records, return offset to first (or eof). + Link allocate(const Link& count) NOEXCEPT; + + /// Report element write completion of count records at link. + void complete(const Link& link, const Link& count) NOEXCEPT; + + /// Get the unified fault condition. + code get_fault() const NOEXCEPT; + + /// Get the unified space required to clear the disk full condition. + size_t get_space() const NOEXCEPT; + + /// Unified resume from disk full condition. + code reload() NOEXCEPT; + +private: + static constexpr auto columns = sizeof...(Sizes); + static constexpr auto key_size = keys::size(); + static constexpr std::array sizes{ Sizes... }; + static constexpr auto is_slab = (std::get(sizes) == max_size_t); + static_assert(!is_slab || is_one(columns), "slab implies single column"); + static_assert(!is_zero(columns), "requires at least one column"); + + template + static constexpr size_t stride() NOEXCEPT; + template + static constexpr size_t strides(std::index_sequence) NOEXCEPT; + + /// Convert between record links and the file's native denomination + /// (elements). Single column file elements are BYTES (width one), so + /// records convert by stride (slabs pass through, links are byte offsets). + /// Aggregate file elements are ROWS, which equal records (pass through). + static constexpr size_t link_to_elements(const Link& link) NOEXCEPT; + static constexpr Link elements_to_link(size_t elements) NOEXCEPT; + + // Thread and remap safe. + storage& files_; +}; + +template +using body = bodys; + +} // namespace database +} // namespace libbitcoin + +#define TEMPLATE template +#define CLASS bodys + +#include + +#undef CLASS +#undef TEMPLATE + +#endif diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 183b864f6..08c5ddfef 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -234,6 +234,10 @@ class store static constexpr bool random = true; static constexpr bool sequential = false; + // Bodies are append-only, so stage writes in anonymous memory where the + // staging backend is built (heads update in place and remain resident). + static constexpr bool staged = true; + static inline path head(const path& folder, const std::string& name) NOEXCEPT { return folder / (name + schema::ext::head); diff --git a/include/bitcoin/database/tables/caches/ecdsa.hpp b/include/bitcoin/database/tables/caches/ecdsa.hpp index 0268258a4..85d864090 100644 --- a/include/bitcoin/database/tables/caches/ecdsa.hpp +++ b/include/bitcoin/database/tables/caches/ecdsa.hpp @@ -424,8 +424,8 @@ class ecdsa_storage { public: ecdsa_storage(const std::filesystem::path& path, size_t size, - size_t rate, bool random_access) NOEXCEPT - : ecdsa_files(path, size, rate, random_access) + size_t rate, bool random_access, bool staged=false) NOEXCEPT + : ecdsa_files(path, size, rate, random_access, staged) { } }; diff --git a/include/bitcoin/database/tables/caches/schnorr.hpp b/include/bitcoin/database/tables/caches/schnorr.hpp index dc0c7aec8..9e0256ee6 100644 --- a/include/bitcoin/database/tables/caches/schnorr.hpp +++ b/include/bitcoin/database/tables/caches/schnorr.hpp @@ -251,8 +251,8 @@ class schnorr_storage { public: schnorr_storage(const std::filesystem::path& path, size_t size, - size_t rate, bool random_access) NOEXCEPT - : schnorr_files(path, size, rate, random_access) + size_t rate, bool random_access, bool staged=false) NOEXCEPT + : schnorr_files(path, size, rate, random_access, staged) { } }; diff --git a/include/bitcoin/database/tables/caches/silent.hpp b/include/bitcoin/database/tables/caches/silent.hpp index 34b4a8e77..40005d97d 100644 --- a/include/bitcoin/database/tables/caches/silent.hpp +++ b/include/bitcoin/database/tables/caches/silent.hpp @@ -154,8 +154,8 @@ class silent_storage { public: silent_storage(const std::filesystem::path& path, size_t size, - size_t rate, bool random_access) NOEXCEPT - : silent_files(path, size, rate, random_access) + size_t rate, bool random_access, bool staged=false) NOEXCEPT + : silent_files(path, size, rate, random_access, staged) { } }; diff --git a/src/memory/mman.cpp b/src/memory/mman.cpp index 46f76c836..a3710a608 100644 --- a/src/memory/mman.cpp +++ b/src/memory/mman.cpp @@ -288,43 +288,67 @@ int sysconf(int) noexcept int fallocate(int fd, int, off_t offset, off_t len) NOEXCEPT { constexpr auto fail = -1; + const auto target = offset + len; - fstore_t store + // Physically allocated bytes (st_blocks is in 512 byte units). + const auto allocated = [fd]() NOEXCEPT -> off_t { - // Prefer contiguous allocation - .fst_flags = F_ALLOCATECONTIG, - - // Allocate from EOF - .fst_posmode = F_PEOFPOSMODE, - - // Ignored for allocation in PEOF mode. - .fst_offset = 0, - - // Delta size - .fst_length = len, - - // Output: actual bytes allocated - .fst_bytesalloc = 0 + struct stat status{}; + return (::fstat(fd, &status) == fail) ? fail : + (status.st_blocks * 512); }; - // Try contiguous allocation. - if (::fcntl(fd, F_PREALLOCATE, &store) == fail) + // Disk full recovery requires that a success fully reserves disk space, + // as writes must then never fail within reserved space. APFS spuriously + // fails F_PREALLOCATE (e.g. when logical EOF lies within blocks that a + // prior call preallocated), so neither its success nor failure is + // trusted: each pass is verified against physical allocation and the + // shortfall retried (F_PEOFPOSMODE allocates from the physical end, so + // progress is monotonic). Only a pass without progress is genuine + // exhaustion, reported as ENOSPC (the recoverable condition). The whole + // file check implies no holes because files grown only through this + // function are never sparse (extension lands in verified allocation). + for (auto current = allocated(); current < target;) { - // Fallback to non-contiguous. - store.fst_flags = F_ALLOCATEALL; - store.fst_bytesalloc = 0; - - // APFS spuriously fails F_PREALLOCATE (e.g. when logical EOF lies - // within blocks preallocated by a prior call), reporting ENOSPC - // despite ample space. Preallocation is therefore best-effort and - // failure is ignored: ftruncate is authoritative, and reports genuine - // exhaustion as its own ENOSPC (via sparse extension at worst). - ::fcntl(fd, F_PREALLOCATE, &store); + if (current == fail) + return fail; + + fstore_t store + { + // Prefer contiguous allocation, from physical EOF (offset + // ignored in PEOF mode), of the verified shortfall. + .fst_flags = F_ALLOCATECONTIG, + .fst_posmode = F_PEOFPOSMODE, + .fst_offset = 0, + .fst_length = target - current, + .fst_bytesalloc = 0 + }; + + // Fallback to non-contiguous allocation. + if (::fcntl(fd, F_PREALLOCATE, &store) == fail) + { + store.fst_flags = F_ALLOCATEALL; + store.fst_bytesalloc = 0; + ::fcntl(fd, F_PREALLOCATE, &store); + } + + const auto next = allocated(); + if (next == fail) + return fail; + + if (next <= current) + { + errno = ENOSPC; + return fail; + } + + current = next; } // Extend file to new size (required for mmap). This is not required on // Linux because fallocate(2) automatically extends file's logical size. - return ::ftruncate(fd, offset + len); + return ::ftruncate(fd, target); } #endif // HAVE_MSC + diff --git a/src/memory/mstage.cpp b/src/memory/mstage.cpp new file mode 100644 index 000000000..45fd61a96 --- /dev/null +++ b/src/memory/mstage.cpp @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#if defined(MANAGE_STAGING) + +#include +#include +#include + +void* mmap_reserve(size_t size) NOEXCEPT +{ + return ::mmap(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); +} + +int mmap_commit(void* address, size_t size) NOEXCEPT +{ + return ::mprotect(address, size, PROT_READ | PROT_WRITE); +} + +int mmap_settle(void* address, size_t size, int fd, size_t offset) NOEXCEPT +{ + return ::mmap(address, size, PROT_READ, MAP_SHARED | MAP_FIXED, fd, + static_cast(offset)) == MAP_FAILED ? -1 : 0; +} + +int mmap_unsettle(void* address, size_t size) NOEXCEPT +{ + return ::mmap(address, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED ? -1 : 0; +} + +// Chunked to avoid platform-specific single-transfer length limits. +constexpr size_t transfer_chunk = size_t{ 1 } << 30; + +bool pread_all(int fd, uint8_t* to, size_t size, size_t offset) NOEXCEPT +{ + while (size > 0) + { + const auto request = std::min(size, transfer_chunk); + const auto result = ::pread(fd, to, request, + static_cast(offset)); + + if (result < 0) + { + if (errno == EINTR) + continue; + + return false; + } + + // Early eof implies the requested range exceeds the file. + if (result == 0) + return false; + + const auto bytes = static_cast(result); + to += bytes; + offset += bytes; + size -= bytes; + } + + return true; +} + +bool pwrite_all(int fd, const uint8_t* from, size_t size, + size_t offset) NOEXCEPT +{ + while (size > 0) + { + const auto request = std::min(size, transfer_chunk); + const auto result = ::pwrite(fd, from, request, + static_cast(offset)); + + if (result <= 0) + { + if (result < 0 && errno == EINTR) + continue; + + return false; + } + + const auto bytes = static_cast(result); + from += bytes; + offset += bytes; + size -= bytes; + } + + return true; +} + +#endif // MANAGE_STAGING diff --git a/src/memory/utilities.cpp b/src/memory/utilities.cpp index 8b80fe307..c86b4d2ca 100644 --- a/src/memory/utilities.cpp +++ b/src/memory/utilities.cpp @@ -23,6 +23,13 @@ #else #include #endif +#if defined(HAVE_APPLE) + #include +#endif +#if defined(HAVE_LINUX) + #include + #include +#endif #include namespace libbitcoin { @@ -48,6 +55,21 @@ uint64_t system_memory() NOEXCEPT status.ullTotalPhys; } +size_t system_pressure() NOEXCEPT +{ + // The kernel low memory resource signal (no configurable threshold). + BOOL low{ FALSE }; + const auto handle = CreateMemoryResourceNotification( + LowMemoryResourceNotification); + + if (handle == NULL) + return zero; + + const auto success = QueryMemoryResourceNotification(handle, &low) != 0; + CloseHandle(handle); + return !success ? zero : (low == FALSE ? size_t{ 1 } : size_t{ 4 }); +} + #else size_t page_size() NOEXCEPT @@ -77,6 +99,49 @@ uint64_t system_memory() NOEXCEPT possible_wide_cast(page_size())); } +size_t system_pressure() NOEXCEPT +{ +#if defined(HAVE_APPLE) + using namespace system; + int level{}; + auto size = sizeof(level); + if (::sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, + nullptr, 0) != 0) + return zero; + + return possible_narrow_sign_cast(level); +#elif defined(HAVE_LINUX) + // PSI (requires CONFIG_PSI): fraction of recent wall time that tasks + // stalled on memory reclaim. A tenth of time stalled is treated as + // genuine pressure, partial (some) as warning and total (full) as + // critical, mapping to the macos memorystatus level semantics. + auto level = zero; + if (const auto file = std::fopen("/proc/pressure/memory", "r")) + { + char line[128]; + double avg10{}; + level = one; + while (!is_null(std::fgets(line, sizeof(line), file))) + { + if ((std::sscanf(line, "some avg10=%lf", &avg10) == 1) && + (avg10 >= 10.0)) + level = std::max(level, size_t{ 2 }); + + if ((std::sscanf(line, "full avg10=%lf", &avg10) == 1) && + (avg10 >= 10.0)) + level = std::max(level, size_t{ 4 }); + } + + std::fclose(file); + } + + return level; +#else + // No platform source. + return zero; +#endif +} + #endif } // namespace database diff --git a/test/memory/mmap.cpp b/test/memory/mmap.cpp index 5e7776ae1..366b54c1c 100644 --- a/test/memory/mmap.cpp +++ b/test/memory/mmap.cpp @@ -789,6 +789,233 @@ BOOST_AUTO_TEST_CASE(mmap__write__read__expected) BOOST_REQUIRE(!instance.get_fault()); } +// The staged parameter engages the anonymous staging backend where built +// (posix); elsewhere it is ignored. Content assertions are identical either +// way, so these cases validate staging on posix and the classic map on win32. + +constexpr auto stage_pattern = [](size_t index) NOEXCEPT +{ + return static_cast(index % 251); +}; + +constexpr auto stage_repattern = [](size_t index) NOEXCEPT +{ + return static_cast((index * 3) % 241); +}; + +BOOST_AUTO_TEST_CASE(mmap__staged__write_flush_write_reload__expected) +{ + constexpr size_t first = 10'000; + constexpr size_t second = 7'000; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, 1, 50, true, true); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + // Write and flush twice, settling from zero and then a page interior. + auto memory = instance.get(instance.allocate(first)); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < first; ++index) + memory.begin()[index] = stage_pattern(index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + + memory = instance.get(instance.allocate(second)); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < second; ++index) + memory.begin()[index] = stage_pattern(first + index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + + // All content is readable through the settled and unsettled regions. + memory = instance.get(); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < first + second; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_pattern(index)); + + memory.reset(); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); + + // All content persists across close and reopen. + map reopened(file, 1, 50, true, true); + BOOST_REQUIRE(!reopened.open()); + BOOST_REQUIRE(!reopened.load()); + BOOST_REQUIRE_EQUAL(reopened.size(), first + second); + + memory = reopened.get(); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < first + second; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_pattern(index)); + + memory.reset(); + BOOST_REQUIRE(!reopened.unload()); + BOOST_REQUIRE(!reopened.close()); + BOOST_REQUIRE(!reopened.get_fault()); +} + +BOOST_AUTO_TEST_CASE(mmap__staged__truncate_below_flush_rewrite__expected) +{ + constexpr size_t initial = 12'000; + constexpr size_t retained = 5'000; + constexpr size_t appended = 4'000; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, 1, 50, true, true); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + auto memory = instance.get(instance.allocate(initial)); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < initial; ++index) + memory.begin()[index] = stage_pattern(index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + + // Truncate below the flushed extent and append replacement content. + BOOST_REQUIRE(instance.truncate(retained)); + BOOST_REQUIRE_EQUAL(instance.allocate(appended), retained); + + memory = instance.get(retained); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < appended; ++index) + memory.begin()[index] = stage_repattern(retained + index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); + + // Retained content precedes replacement content across reopen. + map reopened(file, 1, 50, true, true); + BOOST_REQUIRE(!reopened.open()); + BOOST_REQUIRE(!reopened.load()); + BOOST_REQUIRE_EQUAL(reopened.size(), retained + appended); + + memory = reopened.get(); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < retained; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_pattern(index)); + for (size_t index = retained; index < retained + appended; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_repattern(index)); + + memory.reset(); + BOOST_REQUIRE(!reopened.unload()); + BOOST_REQUIRE(!reopened.close()); + BOOST_REQUIRE(!reopened.get_fault()); +} + +#if defined(MANAGE_STAGING) + +BOOST_AUTO_TEST_CASE(mmap__frontier__staged_out_of_order_completion__expected) +{ + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, 1, 50, true, true); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + // Frontier holds at each extent start until its writes complete. + const auto first = instance.allocate(100); + const auto second = instance.allocate(50); + BOOST_REQUIRE_EQUAL(instance.frontier(), first); + + // Completing the second extent does not advance past the first. + instance.complete(second, 50); + BOOST_REQUIRE_EQUAL(instance.frontier(), first); + + // Completing the first pops both, frontier reaches logical. + instance.complete(first, 100); + BOOST_REQUIRE_EQUAL(instance.frontier(), instance.size()); + + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(mmap__frontier__staged_truncate__discards_extents) +{ + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, 1, 50, true, true); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + const auto first = instance.allocate(100); + instance.complete(first, 100); + const auto second = instance.allocate(50); + BOOST_REQUIRE_EQUAL(instance.frontier(), second); + + // Truncation below the open extent discards it, frontier follows logical. + BOOST_REQUIRE(instance.truncate(first + 100)); + BOOST_REQUIRE_EQUAL(instance.frontier(), instance.size()); + + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +#endif // MANAGE_STAGING + +BOOST_AUTO_TEST_CASE(mmap__unstaged__rewrite_below_flush__expected) +{ + constexpr size_t size = 10'000; + constexpr size_t rewrite = 100; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + // Unstaged (default) retains in-place rewrite of flushed content (heads). + map instance(file, 1, 50, true); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + auto memory = instance.get(instance.allocate(size)); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < size; ++index) + memory.begin()[index] = stage_pattern(index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + + memory = instance.get(); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < rewrite; ++index) + memory.begin()[index] = stage_repattern(index); + + memory.reset(); + BOOST_REQUIRE(!instance.flush()); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); + + map reopened(file, 1, 50, true); + BOOST_REQUIRE(!reopened.open()); + BOOST_REQUIRE(!reopened.load()); + BOOST_REQUIRE_EQUAL(reopened.size(), size); + + memory = reopened.get(); + BOOST_REQUIRE(memory); + for (size_t index = 0; index < rewrite; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_repattern(index)); + for (size_t index = rewrite; index < size; ++index) + BOOST_REQUIRE_EQUAL(memory.begin()[index], stage_pattern(index)); + + memory.reset(); + BOOST_REQUIRE(!reopened.unload()); + BOOST_REQUIRE(!reopened.close()); + BOOST_REQUIRE(!reopened.get_fault()); +} + BOOST_AUTO_TEST_CASE(mmap__unload__shared__unload_locked) { const std::string file = TEST_PATH; diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index 62cd63907..c3a6d88e6 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -1,333 +1,342 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_DATABASE_TEST_MOCKS_CHUNK_STORAGE_HPP -#define LIBBITCOIN_DATABASE_TEST_MOCKS_CHUNK_STORAGE_HPP - -#include -#include -#include "../test.hpp" - -namespace test { - -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - -template -class default_storage - : public Storage -{ -public: - using path = std::filesystem::path; - - default_storage(const path& filename="test", size_t minimum=1, - size_t expansion=0, bool random=true) NOEXCEPT - : Storage(filename, minimum, expansion, random) - { - } -}; - -/// A thread safe storage implementation built on data_chunk(s); the vector- -/// backed substitute for mmap. One data_chunk per column. -template -class chunk_storages - : public database::storage -{ -public: - using path = std::filesystem::path; - - static constexpr size_t columns = sizeof...(Widths); - static_assert(columns >= one, "requires at least one column"); - static constexpr std::array widths{ Widths... }; - static constexpr size_t stride = (Widths + ...); - using paths = std::array; - - /// Scalar construction (is_one(columns)): single backing. - chunk_storages() NOEXCEPT requires (is_one(columns)) - : alias_{}, paths_{ path{ "test" } }, logical_{} - { - } - - chunk_storages(system::data_chunk& reference) NOEXCEPT - requires (is_one(columns)) - : alias_{ &reference }, paths_{ path{ "test" } }, - logical_{ reference.size() } - { - } - - chunk_storages(const path& filename, size_t=1, size_t=0, bool=true) NOEXCEPT - requires (is_one(columns)) - : alias_{}, paths_{ filename }, logical_{} - { - } - - /// Aggregate construction (columns > 1): one backing per column. - chunk_storages(const paths& filenames, size_t=1, size_t=0, - bool=true) NOEXCEPT requires (columns > one) - : alias_{}, paths_{ filenames }, logical_{} - { - } - - ~chunk_storages() = default; - - /// test side door (column 0 buffer). - system::data_chunk& buffer() NOEXCEPT - { - return at(zero); - } - - // storage interface (no-op lifecycle). - // ------------------------------------------------------------------------ - - code get_fault() const NOEXCEPT override - { - return {}; - } - - size_t get_space() const NOEXCEPT override - { - return {}; - } - - code create() const NOEXCEPT override - { - return error::success; - } - - code open() NOEXCEPT override - { - return error::success; - } - - code close() NOEXCEPT override - { - return error::success; - } - - code load() NOEXCEPT override - { - return error::success; - } - - code reload() NOEXCEPT override - { - return error::success; - } - - code flush() NOEXCEPT override - { - return error::success; - } - - code unload() NOEXCEPT override - { - return error::success; - } - - code shrink() NOEXCEPT override - { - return error::success; - } - - code dump(const path&) const NOEXCEPT override - { - return error::success; - } - - const path& file() const NOEXCEPT override - { - return paths_[0]; - } - - // sizing (row-denominated, shared across columns). - // ------------------------------------------------------------------------ - - size_t capacity() const NOEXCEPT override - { - std::shared_lock field_lock(field_mutex_); - return is_zero(widths[0]) ? logical_ : at(zero).size() / widths[0]; - } - - size_t size() const NOEXCEPT override - { - std::shared_lock field_lock(field_mutex_); - return logical_; - } - - bool truncate(size_t count) NOEXCEPT override - { - std::unique_lock field_lock(field_mutex_); - if (count > logical_) - return false; - - logical_ = count; - return true; - } - - bool expand(size_t count) NOEXCEPT override - { - std::unique_lock field_lock(field_mutex_); - - for (size_t column{}; column < columns; ++column) - if (to_capacity(count, column) > at(column).max_size()) - return false; - - if (count <= logical_) - return true; - - logical_ = count; - for (size_t column{}; column < columns; ++column) - if (to_capacity(logical_, column) > at(column).size()) - at(column).resize(to_capacity(logical_, column)); - - return true; - } - - bool reserve(size_t count) NOEXCEPT override - { - std::unique_lock field_lock(field_mutex_); - if (system::is_add_overflow(logical_, count)) - return false; - - const auto end = logical_ + count; - for (size_t column{}; column < columns; ++column) - if (to_capacity(end, column) > at(column).max_size()) - return false; - - for (size_t column{}; column < columns; ++column) - if (to_capacity(end, column) > at(column).size()) - at(column).resize(to_capacity(end, column)); - - return true; - } - - size_t allocate(size_t count) NOEXCEPT override - { - std::unique_lock field_lock(field_mutex_); - if (system::is_add_overflow(logical_, count)) - return chunk_storages::eof; - - const auto end = logical_ + count; - for (size_t column{}; column < columns; ++column) - if (to_capacity(end, column) > at(column).max_size()) - return chunk_storages::eof; - - std::unique_lock map_lock(map_mutex_); - const auto link = logical_; - - logical_ = end; - for (size_t column{}; column < columns; ++column) - if (to_capacity(logical_, column) > at(column).size()) - at(column).resize(to_capacity(logical_, column)); - - return link; - } - - // access - // ------------------------------------------------------------------------ - - memory get_filled(size_t offset, size_t size, - uint8_t backfill) NOEXCEPT override - { - { - std::unique_lock field_lock(field_mutex_); - if (system::is_add_overflow(offset, size)) - return {}; - - std::unique_lock map_lock(map_mutex_); - auto& buffer = at(zero); - const auto minimum = offset + size; - if (minimum > buffer.size()) - buffer.resize(minimum, backfill); - - // Reflect byte growth of column 0 into the shared row count. - if (!is_zero(widths[0]) && (minimum / widths[0]) > logical_) - logical_ = (minimum / widths[0]); - } - - return get(offset); - } - - memory get_capacity(size_t offset=zero) const NOEXCEPT override - { - auto& buffer = at(zero); - accessor out{ map_mutex_ }; - out.assign(get_raw(offset), std::next(buffer.data(), buffer.size())); - return out; - } - - memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override - { - return std::next(at(zero).data(), offset); - } - - memory::iterator get_raw_at(size_t column, - size_t offset=zero) const NOEXCEPT override - { - return std::next(at(column).data(), offset); - } - - memory get(size_t offset=zero) const NOEXCEPT override - { - return get_at(zero, offset); - } - - memory get_at(size_t column, size_t offset=zero) const NOEXCEPT override - { - using namespace system; - auto data = at(column).data(); - const auto allocated = size() * widths.at(column); - accessor out(map_mutex_); - out.assign(std::next(data, offset), std::next(data, allocated)); - return out; - } - - // This is protected by mutex. - mutable std::array buffers_{}; - -private: - // Byte capacity of a column for the given shared row count. - static constexpr size_t to_capacity(size_t rows, size_t column) NOEXCEPT - { - return rows * widths.at(column); - } - - // Column backing selector: N owned column chunks, with an optional - // external alias overriding column 0 (single-column reference ctor). - system::data_chunk& at(size_t column) const NOEXCEPT - { - return (is_zero(column) && !is_null(alias_)) ? *alias_ : - buffers_.at(column); - } - - // These are protected by mutex. - system::data_chunk* const alias_; - size_t logical_{}; - - // These are thread safe. - const paths paths_; - mutable std::shared_mutex field_mutex_{}; - mutable std::shared_mutex map_mutex_{}; -}; - -BC_POP_WARNING() - -using chunk_storage = chunk_storages; - -} // namespace test - -#endif +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_TEST_MOCKS_CHUNK_STORAGE_HPP +#define LIBBITCOIN_DATABASE_TEST_MOCKS_CHUNK_STORAGE_HPP + +#include +#include +#include "../test.hpp" + +namespace test { + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +template +class default_storage + : public Storage +{ +public: + using path = std::filesystem::path; + + default_storage(const path& filename="test", size_t minimum=1, + size_t expansion=0, bool random=true) NOEXCEPT + : Storage(filename, minimum, expansion, random) + { + } +}; + +/// A thread safe storage implementation built on data_chunk(s); the vector- +/// backed substitute for mmap. One data_chunk per column. +template +class chunk_storages + : public database::storage +{ +public: + using path = std::filesystem::path; + + static constexpr size_t columns = sizeof...(Widths); + static_assert(columns >= one, "requires at least one column"); + static constexpr std::array widths{ Widths... }; + static constexpr size_t stride = (Widths + ...); + using paths = std::array; + + /// Scalar construction (is_one(columns)): single backing. + chunk_storages() NOEXCEPT requires (is_one(columns)) + : alias_{}, paths_{ path{ "test" } }, logical_{} + { + } + + chunk_storages(system::data_chunk& reference) NOEXCEPT + requires (is_one(columns)) + : alias_{ &reference }, paths_{ path{ "test" } }, + logical_{ reference.size() } + { + } + + chunk_storages(const path& filename, size_t=1, size_t=0, bool=true, + bool=false) NOEXCEPT requires (is_one(columns)) + : alias_{}, paths_{ filename }, logical_{} + { + } + + /// Aggregate construction (columns > 1): one backing per column. + chunk_storages(const paths& filenames, size_t=1, size_t=0, bool=true, + bool=false) NOEXCEPT requires (columns > one) + : alias_{}, paths_{ filenames }, logical_{} + { + } + + ~chunk_storages() = default; + + /// test side door (column 0 buffer). + system::data_chunk& buffer() NOEXCEPT + { + return at(zero); + } + + // storage interface (no-op lifecycle). + // ------------------------------------------------------------------------ + + code get_fault() const NOEXCEPT override + { + return {}; + } + + size_t get_space() const NOEXCEPT override + { + return {}; + } + + code create() const NOEXCEPT override + { + return error::success; + } + + code open() NOEXCEPT override + { + return error::success; + } + + code close() NOEXCEPT override + { + return error::success; + } + + code load() NOEXCEPT override + { + return error::success; + } + + code reload() NOEXCEPT override + { + return error::success; + } + + code flush() NOEXCEPT override + { + return error::success; + } + + code unload() NOEXCEPT override + { + return error::success; + } + + code shrink() NOEXCEPT override + { + return error::success; + } + + code dump(const path&) const NOEXCEPT override + { + return error::success; + } + + const path& file() const NOEXCEPT override + { + return paths_[0]; + } + + // sizing (row-denominated, shared across columns). + // ------------------------------------------------------------------------ + + size_t capacity() const NOEXCEPT override + { + std::shared_lock field_lock(field_mutex_); + return is_zero(widths[0]) ? logical_ : at(zero).size() / widths[0]; + } + + size_t size() const NOEXCEPT override + { + std::shared_lock field_lock(field_mutex_); + return logical_; + } + + bool truncate(size_t count) NOEXCEPT override + { + std::unique_lock field_lock(field_mutex_); + if (count > logical_) + return false; + + logical_ = count; + return true; + } + + bool expand(size_t count) NOEXCEPT override + { + std::unique_lock field_lock(field_mutex_); + + for (size_t column{}; column < columns; ++column) + if (to_capacity(count, column) > at(column).max_size()) + return false; + + if (count <= logical_) + return true; + + logical_ = count; + for (size_t column{}; column < columns; ++column) + if (to_capacity(logical_, column) > at(column).size()) + at(column).resize(to_capacity(logical_, column)); + + return true; + } + + bool reserve(size_t count) NOEXCEPT override + { + std::unique_lock field_lock(field_mutex_); + if (system::is_add_overflow(logical_, count)) + return false; + + const auto end = logical_ + count; + for (size_t column{}; column < columns; ++column) + if (to_capacity(end, column) > at(column).max_size()) + return false; + + for (size_t column{}; column < columns; ++column) + if (to_capacity(end, column) > at(column).size()) + at(column).resize(to_capacity(end, column)); + + return true; + } + + size_t allocate(size_t count) NOEXCEPT override + { + std::unique_lock field_lock(field_mutex_); + if (system::is_add_overflow(logical_, count)) + return chunk_storages::eof; + + const auto end = logical_ + count; + for (size_t column{}; column < columns; ++column) + if (to_capacity(end, column) > at(column).max_size()) + return chunk_storages::eof; + + std::unique_lock map_lock(map_mutex_); + const auto link = logical_; + + logical_ = end; + for (size_t column{}; column < columns; ++column) + if (to_capacity(logical_, column) > at(column).size()) + at(column).resize(to_capacity(logical_, column)); + + return link; + } + + void complete(size_t, size_t) NOEXCEPT override + { + } + + size_t frontier() const NOEXCEPT override + { + return size(); + } + + // access + // ------------------------------------------------------------------------ + + memory get_filled(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT override + { + { + std::unique_lock field_lock(field_mutex_); + if (system::is_add_overflow(offset, size)) + return {}; + + std::unique_lock map_lock(map_mutex_); + auto& buffer = at(zero); + const auto minimum = offset + size; + if (minimum > buffer.size()) + buffer.resize(minimum, backfill); + + // Reflect byte growth of column 0 into the shared row count. + if (!is_zero(widths[0]) && (minimum / widths[0]) > logical_) + logical_ = (minimum / widths[0]); + } + + return get(offset); + } + + memory get_capacity(size_t offset=zero) const NOEXCEPT override + { + auto& buffer = at(zero); + accessor out{ map_mutex_ }; + out.assign(get_raw(offset), std::next(buffer.data(), buffer.size())); + return out; + } + + memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override + { + return std::next(at(zero).data(), offset); + } + + memory::iterator get_raw_at(size_t column, + size_t offset=zero) const NOEXCEPT override + { + return std::next(at(column).data(), offset); + } + + memory get(size_t offset=zero) const NOEXCEPT override + { + return get_at(zero, offset); + } + + memory get_at(size_t column, size_t offset=zero) const NOEXCEPT override + { + using namespace system; + auto data = at(column).data(); + const auto allocated = size() * widths.at(column); + accessor out(map_mutex_); + out.assign(std::next(data, offset), std::next(data, allocated)); + return out; + } + + // This is protected by mutex. + mutable std::array buffers_{}; + +private: + // Byte capacity of a column for the given shared row count. + static constexpr size_t to_capacity(size_t rows, size_t column) NOEXCEPT + { + return rows * widths.at(column); + } + + // Column backing selector: N owned column chunks, with an optional + // external alias overriding column 0 (single-column reference ctor). + system::data_chunk& at(size_t column) const NOEXCEPT + { + return (is_zero(column) && !is_null(alias_)) ? *alias_ : + buffers_.at(column); + } + + // These are protected by mutex. + system::data_chunk* const alias_; + size_t logical_{}; + + // These are thread safe. + const paths paths_; + mutable std::shared_mutex field_mutex_{}; + mutable std::shared_mutex map_mutex_{}; +}; + +BC_POP_WARNING() + +using chunk_storage = chunk_storages; + +} // namespace test + +#endif