From f68d36b517782b79d86f280db6125f52a0bc9b81 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 15:38:44 -0400 Subject: [PATCH 1/8] Make accessor non-templated (always shared_mutex). --- builds/cmake/install-cmake.sh | 16 ++-- builds/cmake/install-presets.sh | 16 ++-- builds/gnu/Makefile.am | 1 - builds/gnu/install-gnu.sh | 16 ++-- .../libbitcoin-database.vcxproj | 1 - .../libbitcoin-database.vcxproj.filters | 3 - .../libbitcoin-database.vcxproj | 1 - .../libbitcoin-database.vcxproj.filters | 3 - .../bitcoin/database/impl/memory/accessor.ipp | 96 ------------------- .../database/impl/memory/mmap_dispatch.ipp | 4 +- include/bitcoin/database/memory/accessor.hpp | 76 +++++++++++---- include/bitcoin/database/memory/mmap.hpp | 1 - test/memory/accessor.cpp | 2 +- test/mocks/chunk_storage.hpp | 4 +- 14 files changed, 89 insertions(+), 151 deletions(-) delete mode 100644 include/bitcoin/database/impl/memory/accessor.ipp diff --git a/builds/cmake/install-cmake.sh b/builds/cmake/install-cmake.sh index 31eb60046..54f9b3769 100755 --- a/builds/cmake/install-cmake.sh +++ b/builds/cmake/install-cmake.sh @@ -446,21 +446,23 @@ main() UltrafastSecp256k1_FLAGS=() UltrafastSecp256k1_OPTIONS=( - "-DSECP256K1_BUILD_TESTS=OFF" - "-DSECP256K1_BUILD_BENCH=OFF" - "-DSECP256K1_BUILD_EXAMPLES=OFF" - "-DSECP256K1_BUILD_JAVA=OFF" - "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_BUILD_CABI=ON" "-DSECP256K1_BUILD_CPU=ON" "-DSECP256K1_BUILD_SHIM=ON" + "-DSECP256K1_USE_ULTRAFAST=ON" "-DSECP256K1_BUILD_CUDA=ON" "-DSECP256K1_BUILD_ROCM=ON" "-DSECP256K1_BUILD_OPENCL=ON" "-DSECP256K1_BUILD_METAL=ON" + "-DSECP256K1_BUILD_BENCH=OFF" + "-DSECP256K1_BUILD_EXAMPLES=OFF" + "-DSECP256K1_BUILD_JAVA=OFF" + "-DSECP256K1_BUILD_TESTS=OFF" + "-DSECP256K1_SHIM_BUILD_SHARED=OFF" + "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_INSTALL=ON" - "-DSECP256K1_INSTALL_PKGCONFIG=ON" - "-DSECP256K1_USE_ULTRAFAST=ON") + "-DSECP256K1_SHIM_INSTALL=ON" + "-DSECP256K1_INSTALL_PKGCONFIG=ON") libbitcoin_system_FLAGS=() diff --git a/builds/cmake/install-presets.sh b/builds/cmake/install-presets.sh index a16a2dbc5..700ec5c99 100755 --- a/builds/cmake/install-presets.sh +++ b/builds/cmake/install-presets.sh @@ -462,21 +462,23 @@ main() UltrafastSecp256k1_FLAGS=() UltrafastSecp256k1_OPTIONS=( - "-DSECP256K1_BUILD_TESTS=OFF" - "-DSECP256K1_BUILD_BENCH=OFF" - "-DSECP256K1_BUILD_EXAMPLES=OFF" - "-DSECP256K1_BUILD_JAVA=OFF" - "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_BUILD_CABI=ON" "-DSECP256K1_BUILD_CPU=ON" "-DSECP256K1_BUILD_SHIM=ON" + "-DSECP256K1_USE_ULTRAFAST=ON" "-DSECP256K1_BUILD_CUDA=ON" "-DSECP256K1_BUILD_ROCM=ON" "-DSECP256K1_BUILD_OPENCL=ON" "-DSECP256K1_BUILD_METAL=ON" + "-DSECP256K1_BUILD_BENCH=OFF" + "-DSECP256K1_BUILD_EXAMPLES=OFF" + "-DSECP256K1_BUILD_JAVA=OFF" + "-DSECP256K1_BUILD_TESTS=OFF" + "-DSECP256K1_SHIM_BUILD_SHARED=OFF" + "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_INSTALL=ON" - "-DSECP256K1_INSTALL_PKGCONFIG=ON" - "-DSECP256K1_USE_ULTRAFAST=ON") + "-DSECP256K1_SHIM_INSTALL=ON" + "-DSECP256K1_INSTALL_PKGCONFIG=ON") libbitcoin_system_FLAGS=() diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 066f222b7..722d82ddc 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -91,7 +91,6 @@ include_bitcoin_database_impl_memorydir = \ ${includedir}/bitcoin/database/impl/memory include_bitcoin_database_impl_memory_HEADERS = \ - ${srcdir}/../../include/bitcoin/database/impl/memory/accessor.ipp \ ${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 \ diff --git a/builds/gnu/install-gnu.sh b/builds/gnu/install-gnu.sh index 1a67860ee..149ed7be1 100755 --- a/builds/gnu/install-gnu.sh +++ b/builds/gnu/install-gnu.sh @@ -474,21 +474,23 @@ main() UltrafastSecp256k1_FLAGS=() UltrafastSecp256k1_OPTIONS=( - "-DSECP256K1_BUILD_TESTS=OFF" - "-DSECP256K1_BUILD_BENCH=OFF" - "-DSECP256K1_BUILD_EXAMPLES=OFF" - "-DSECP256K1_BUILD_JAVA=OFF" - "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_BUILD_CABI=ON" "-DSECP256K1_BUILD_CPU=ON" "-DSECP256K1_BUILD_SHIM=ON" + "-DSECP256K1_USE_ULTRAFAST=ON" "-DSECP256K1_BUILD_CUDA=ON" "-DSECP256K1_BUILD_ROCM=ON" "-DSECP256K1_BUILD_OPENCL=ON" "-DSECP256K1_BUILD_METAL=ON" + "-DSECP256K1_BUILD_BENCH=OFF" + "-DSECP256K1_BUILD_EXAMPLES=OFF" + "-DSECP256K1_BUILD_JAVA=OFF" + "-DSECP256K1_BUILD_TESTS=OFF" + "-DSECP256K1_SHIM_BUILD_SHARED=OFF" + "-DUFSECP_BUILD_SHARED=OFF" "-DSECP256K1_INSTALL=ON" - "-DSECP256K1_INSTALL_PKGCONFIG=ON" - "-DSECP256K1_USE_ULTRAFAST=ON") + "-DSECP256K1_SHIM_INSTALL=ON" + "-DSECP256K1_INSTALL_PKGCONFIG=ON") libbitcoin_system_FLAGS=() diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index 975320042..a9773d5fd 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -222,7 +222,6 @@ - diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index e6d4b5e27..a090afe99 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -379,9 +379,6 @@ - - include\bitcoin\database\impl\memory - include\bitcoin\database\impl\memory diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index 15d03af47..f122e4d86 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -222,7 +222,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index e6d4b5e27..a090afe99 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -379,9 +379,6 @@ - - include\bitcoin\database\impl\memory - include\bitcoin\database\impl\memory diff --git a/include/bitcoin/database/impl/memory/accessor.ipp b/include/bitcoin/database/impl/memory/accessor.ipp deleted file mode 100644 index ddf104746..000000000 --- a/include/bitcoin/database/impl/memory/accessor.ipp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * 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_ACCESSOR_IPP -#define LIBBITCOIN_DATABASE_MEMORY_ACCESSOR_IPP - -////#include -#include - -// Zero/negative size is allowed (automatically handled by bc streams). - -namespace libbitcoin { -namespace database { - -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - -TEMPLATE -inline CLASS::accessor(Mutex& mutex) NOEXCEPT - : shared_lock_(mutex) -{ -} - -TEMPLATE -inline void CLASS::assign(uint8_t* begin, uint8_t* end) NOEXCEPT -{ - begin_ = begin; - end_ = end; - ////BC_ASSERT(!system::is_negative(size())); -} - -TEMPLATE -inline uint8_t* CLASS::offset(size_t bytes) const NOEXCEPT -{ - if (system::is_greater(bytes, size())) - return nullptr; - - BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) - return begin_ + bytes; - BC_POP_WARNING() - ////return std::next(begin_, bytes); -} - -TEMPLATE -inline ptrdiff_t CLASS::size() const NOEXCEPT -{ - BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) - return system::possible_narrow_and_sign_cast(end_ - begin_); - BC_POP_WARNING() - ////return std::distance(begin_, end_); -} - -TEMPLATE -inline uint8_t* CLASS::data() const NOEXCEPT -{ - return begin(); -} - -TEMPLATE -inline CLASS::operator system::data_slab() const NOEXCEPT -{ - return { begin(), end() }; -}; - -TEMPLATE -inline uint8_t* CLASS::begin() const NOEXCEPT -{ - return begin_; -} - -TEMPLATE -inline uint8_t* CLASS::end() const NOEXCEPT -{ - return end_; -} - -BC_POP_WARNING() - -} // namespace database -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp index efbbdeec9..69e27c2e8 100644 --- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp +++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp @@ -38,7 +38,7 @@ memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT { const auto allocated = to_width(capacity()); - const auto ptr = std::make_shared(remap_mutex_); + const auto ptr = std::make_shared(remap_mutex_); if (!loaded_ || is_null(ptr)) return {}; @@ -109,7 +109,7 @@ memory_ptr CLASS::get_at(size_t column, size_t offset) const NOEXCEPT const auto allocated = size() * widths.at(column); // Takes a shared lock on remap_mutex_ until destruct, blocking remap. - const auto ptr = std::make_shared(remap_mutex_); + const auto ptr = std::make_shared(remap_mutex_); // loaded_ update is precluded by above lock, making this read atomic. if (!loaded_ || is_null(ptr)) diff --git a/include/bitcoin/database/memory/accessor.hpp b/include/bitcoin/database/memory/accessor.hpp index 4fe08225a..a6560201f 100644 --- a/include/bitcoin/database/memory/accessor.hpp +++ b/include/bitcoin/database/memory/accessor.hpp @@ -27,51 +27,89 @@ namespace libbitcoin { namespace database { /// Shared r/w access to a memory buffer, mutex blocks memory remap. -template +/// Zero/negative size is allowed (automatically handled by bc streams). class accessor : public memory { public: - DELETE_COPY_MOVE_DESTRUCT(accessor); + DELETE_COPY(accessor); + DEFAULT_MOVE(accessor); + virtual ~accessor() = default; /// Mutex guards against remap while object in scope. - inline accessor(Mutex& mutex) NOEXCEPT; + inline accessor(std::shared_mutex& mutex) NOEXCEPT + : shared_lock_(mutex) + { + } /// Set the buffer, where end is within allocated space. /// End should be initialized to logical space though that may contract or /// expand during accessor lifetime. The only guarantee offered by end is /// that it remains within allocated space and is initially logical space. - inline void assign(uint8_t* begin, uint8_t* end) NOEXCEPT; + inline void assign(uint8_t* begin, uint8_t* end) NOEXCEPT + { + begin_ = begin; + end_ = end; + ////BC_ASSERT(!system::is_negative(size())); + } + + /// memory interface + /// ----------------------------------------------------------------------- /// Return an offset from begin, nullptr if end or past end. - inline uint8_t* offset(size_t bytes) const NOEXCEPT override; + inline uint8_t* offset(size_t bytes) const NOEXCEPT override + { + if (system::is_greater(bytes, size())) + return nullptr; + + BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) + return begin_ + bytes; + BC_POP_WARNING() + ////return std::next(begin_, bytes); + } /// The logical buffer size (from begin to end). - inline ptrdiff_t size() const NOEXCEPT override; + inline ptrdiff_t size() const NOEXCEPT override + { + BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) + return system::possible_narrow_and_sign_cast(end_ - begin_); + BC_POP_WARNING() + ////return std::distance(begin_, end_); + } /// Alias for begin. - inline uint8_t* data() const NOEXCEPT override; + inline uint8_t* data() const NOEXCEPT override + { + return begin(); + } /// Get logical buffer (guarded against remap only). - inline operator system::data_slab() const NOEXCEPT override; - inline uint8_t* begin() const NOEXCEPT override; - inline uint8_t* end() const NOEXCEPT override; + inline operator system::data_slab() const NOEXCEPT override + { + return { begin(), end() }; + }; + + /// Buffer start. + inline uint8_t* begin() const NOEXCEPT override + { + return begin_; + } + + /// Buffer end. + inline uint8_t* end() const NOEXCEPT override + { + return end_; + } private: + using lock = std::shared_lock; + + lock shared_lock_; uint8_t* begin_{}; uint8_t* end_{}; - std::shared_lock shared_lock_; }; } // namespace database } // namespace libbitcoin -#define TEMPLATE template -#define CLASS accessor - -#include - -#undef CLASS -#undef TEMPLATE - #endif diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index c425b981a..216884a6b 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -174,7 +174,6 @@ class mmap private: static constexpr auto fail = -1; - using access = accessor; using sequence = std::make_index_sequence; // mman dispatch, not thread safe. diff --git a/test/memory/accessor.cpp b/test/memory/accessor.cpp index 6293fd30e..04238e768 100644 --- a/test/memory/accessor.cpp +++ b/test/memory/accessor.cpp @@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(accessor__construct_shared_mutex__unassigned__nulls) BOOST_AUTO_TEST_CASE(accessor__destruct__shared_lock__released) { std::shared_mutex mutex; - auto access = std::make_shared>(mutex); + auto access = std::make_shared(mutex); BOOST_REQUIRE(!mutex.try_lock()); access.reset(); BOOST_REQUIRE(mutex.try_lock()); diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index b98d4fa53..df7e5f192 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -244,7 +244,7 @@ class chunk_storages { using namespace system; auto& buffer = at(zero); - const auto ptr = emplace_shared>(map_mutex_); + const auto ptr = emplace_shared(map_mutex_); ptr->assign(get_raw(offset), std::next(buffer.data(), buffer.size())); return ptr; } @@ -285,7 +285,7 @@ class chunk_storages using namespace system; auto data = at(column).data(); const auto allocated = size() * widths.at(column); - const auto ptr = emplace_shared>(map_mutex_); + const auto ptr = emplace_shared(map_mutex_); ptr->assign(std::next(data, offset), std::next(data, allocated)); return ptr; } From d9f1fadda47502783391246ab6e3688112f9a911 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 16:26:16 -0400 Subject: [PATCH 2/8] Drop memory interface into concrete type. --- builds/gnu/Makefile.am | 1 - .../libbitcoin-database.vcxproj | 1 - .../libbitcoin-database.vcxproj.filters | 3 - .../libbitcoin-database.vcxproj | 1 - .../libbitcoin-database.vcxproj.filters | 3 - include/bitcoin/database.hpp | 1 - include/bitcoin/database/memory/accessor.hpp | 42 +++++++++----- .../database/memory/interfaces/memory.hpp | 57 ------------------- .../database/memory/interfaces/storage.hpp | 2 +- include/bitcoin/database/memory/memory.hpp | 1 - include/bitcoin/database/memory/mmap.hpp | 1 - include/bitcoin/database/memory/reader.hpp | 2 +- include/bitcoin/database/memory/streamers.hpp | 2 +- test/memory/accessor.cpp | 3 +- 14 files changed, 33 insertions(+), 87 deletions(-) delete mode 100644 include/bitcoin/database/memory/interfaces/memory.hpp diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 722d82ddc..779dd0c99 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -230,7 +230,6 @@ include_bitcoin_database_memory_interfacesdir = \ ${includedir}/bitcoin/database/memory/interfaces include_bitcoin_database_memory_interfaces_HEADERS = \ - ${srcdir}/../../include/bitcoin/database/memory/interfaces/memory.hpp \ ${srcdir}/../../include/bitcoin/database/memory/interfaces/storage.hpp include_bitcoin_database_primitivesdir = \ diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index a9773d5fd..efedb59c9 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -153,7 +153,6 @@ - diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index a090afe99..0d8fcc5f1 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -176,9 +176,6 @@ include\bitcoin\database\memory - - include\bitcoin\database\memory\interfaces - include\bitcoin\database\memory\interfaces diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index f122e4d86..3cba41c76 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -153,7 +153,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index a090afe99..0d8fcc5f1 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -176,9 +176,6 @@ include\bitcoin\database\memory - - include\bitcoin\database\memory\interfaces - include\bitcoin\database\memory\interfaces diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index 8aa84be17..2989b4496 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include diff --git a/include/bitcoin/database/memory/accessor.hpp b/include/bitcoin/database/memory/accessor.hpp index a6560201f..c4a1cb541 100644 --- a/include/bitcoin/database/memory/accessor.hpp +++ b/include/bitcoin/database/memory/accessor.hpp @@ -21,24 +21,32 @@ #include #include -#include namespace libbitcoin { namespace database { /// Shared r/w access to a memory buffer, mutex blocks memory remap. /// Zero/negative size is allowed (automatically handled by bc streams). -class accessor - : public memory +class accessor final { public: + typedef uint8_t value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + typedef std::shared_ptr ptr; + DELETE_COPY(accessor); DEFAULT_MOVE(accessor); - virtual ~accessor() = default; + + /// Fault construction, invalid object, no lock taken. + inline accessor() NOEXCEPT + : shared_lock_{} + { + } /// Mutex guards against remap while object in scope. inline accessor(std::shared_mutex& mutex) NOEXCEPT - : shared_lock_(mutex) + : shared_lock_{ mutex } { } @@ -56,8 +64,13 @@ class accessor /// memory interface /// ----------------------------------------------------------------------- + inline operator bool() const NOEXCEPT + { + return !is_null(begin_); + } + /// Return an offset from begin, nullptr if end or past end. - inline uint8_t* offset(size_t bytes) const NOEXCEPT override + inline uint8_t* offset(size_t bytes) const NOEXCEPT { if (system::is_greater(bytes, size())) return nullptr; @@ -69,7 +82,7 @@ class accessor } /// The logical buffer size (from begin to end). - inline ptrdiff_t size() const NOEXCEPT override + inline ptrdiff_t size() const NOEXCEPT { BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) return system::possible_narrow_and_sign_cast(end_ - begin_); @@ -78,37 +91,38 @@ class accessor } /// Alias for begin. - inline uint8_t* data() const NOEXCEPT override + inline uint8_t* data() const NOEXCEPT { return begin(); } /// Get logical buffer (guarded against remap only). - inline operator system::data_slab() const NOEXCEPT override + inline operator system::data_slab() const NOEXCEPT { return { begin(), end() }; }; /// Buffer start. - inline uint8_t* begin() const NOEXCEPT override + inline uint8_t* begin() const NOEXCEPT { return begin_; } /// Buffer end. - inline uint8_t* end() const NOEXCEPT override + inline uint8_t* end() const NOEXCEPT { return end_; } private: - using lock = std::shared_lock; - - lock shared_lock_; uint8_t* begin_{}; uint8_t* end_{}; + std::shared_lock shared_lock_; }; +using memory = accessor; +using memory_ptr = accessor::ptr; + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/memory/interfaces/memory.hpp b/include/bitcoin/database/memory/interfaces/memory.hpp deleted file mode 100644 index d033c5c91..000000000 --- a/include/bitcoin/database/memory/interfaces/memory.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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_INTERFACES_MEMORY_HPP -#define LIBBITCOIN_DATABASE_MEMORY_INTERFACES_MEMORY_HPP - -#include -#include - -namespace libbitcoin { -namespace database { - -/// Protected memory access interface. -class memory -{ -public: - typedef uint8_t value_type; - typedef value_type* iterator; - typedef const value_type* const_iterator; - typedef std::shared_ptr ptr; - - /// Return an offset from begin, nullptr if end or past end. - virtual uint8_t* offset(size_t value) const NOEXCEPT = 0; - - /// The logical buffer size (from begin to end). - virtual ptrdiff_t size() const NOEXCEPT = 0; - - /// Alias begin. - virtual uint8_t* data() const NOEXCEPT = 0; - - /// Get logical buffer. - virtual operator system::data_slab() const NOEXCEPT = 0; - virtual uint8_t* begin() const NOEXCEPT = 0; - virtual uint8_t* end() const NOEXCEPT = 0; -}; - -typedef memory::ptr memory_ptr; - -} // namespace database -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index e190c1ebb..76782c9d9 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -21,7 +21,7 @@ #include #include -#include +#include namespace libbitcoin { namespace database { diff --git a/include/bitcoin/database/memory/memory.hpp b/include/bitcoin/database/memory/memory.hpp index 0d8002f60..8742fba66 100644 --- a/include/bitcoin/database/memory/memory.hpp +++ b/include/bitcoin/database/memory/memory.hpp @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index 216884a6b..8c37ceb9a 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -27,7 +27,6 @@ #include #include #include -#include #include namespace libbitcoin { diff --git a/include/bitcoin/database/memory/reader.hpp b/include/bitcoin/database/memory/reader.hpp index 97e31caac..351e062dc 100644 --- a/include/bitcoin/database/memory/reader.hpp +++ b/include/bitcoin/database/memory/reader.hpp @@ -20,7 +20,7 @@ #define LIBBITCOIN_DATABASE_MEMORY_READER_HPP #include -#include +#include namespace libbitcoin { namespace database { diff --git a/include/bitcoin/database/memory/streamers.hpp b/include/bitcoin/database/memory/streamers.hpp index f64868937..652a4c01f 100644 --- a/include/bitcoin/database/memory/streamers.hpp +++ b/include/bitcoin/database/memory/streamers.hpp @@ -20,7 +20,7 @@ #define LIBBITCOIN_DATABASE_MEMORY_STREAMERS_HPP #include -#include +#include namespace libbitcoin { namespace database { diff --git a/test/memory/accessor.cpp b/test/memory/accessor.cpp index 04238e768..4bcfd1fa5 100644 --- a/test/memory/accessor.cpp +++ b/test/memory/accessor.cpp @@ -22,10 +22,11 @@ BOOST_AUTO_TEST_SUITE(accessor_tests) using namespace system; -BOOST_AUTO_TEST_CASE(accessor__construct_shared_mutex__unassigned__nulls) +BOOST_AUTO_TEST_CASE(accessor__construct_shared_mutex__unassigned__invalid_nulls) { std::shared_mutex mutex; accessor instance(mutex); + BOOST_REQUIRE(!instance); BOOST_REQUIRE(is_null(instance.data())); BOOST_REQUIRE(is_null(instance.begin())); BOOST_REQUIRE(is_null(instance.end())); From e19243a0b5a0c22a1196eb8ed55140a9072ce1a6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 17:27:45 -0400 Subject: [PATCH 3/8] Draft impl of no-alloc/interlock on nomaps table row writers. --- .../database/impl/memory/mmap_dispatch.ipp | 28 +++++++++++++++++ .../database/impl/primitives/manager.ipp | 30 +++++++++++++++++-- .../database/impl/primitives/nomaps.ipp | 17 +++++------ .../database/impl/query/batch/ecdsa.ipp | 8 ++--- .../database/impl/query/batch/schnorr.ipp | 8 ++--- .../database/impl/query/batch/silent.ipp | 6 ++-- .../database/memory/interfaces/storage.hpp | 9 ++++++ include/bitcoin/database/memory/mmap.hpp | 6 ++++ .../bitcoin/database/primitives/column.hpp | 7 +++-- .../bitcoin/database/primitives/manager.hpp | 6 +++- .../bitcoin/database/primitives/nomaps.hpp | 6 ++-- test/mocks/chunk_storage.hpp | 15 ++++++++++ 12 files changed, 117 insertions(+), 29 deletions(-) diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp index 69e27c2e8..6bd33d17c 100644 --- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp +++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp @@ -121,6 +121,34 @@ memory_ptr CLASS::get_at(size_t column, size_t offset) const NOEXCEPT return ptr; } +TEMPLATE +memory CLASS::get1(size_t offset) const NOEXCEPT +{ + return get_at1(zero, offset); +} + +TEMPLATE +memory CLASS::get_at1(size_t column, size_t offset) const NOEXCEPT +{ + if (column >= columns) + return {}; + + // Obtaining size before access prevents mutual mutex wait (deadlock). + const auto allocated = size() * widths.at(column); + + // Takes a shared lock on remap_mutex_ until destruct, blocking remap. + memory out{ remap_mutex_ }; + + // loaded_ update is precluded by above lock, making this read atomic. + if (!loaded_) + return {}; + + // With offset > size the assignment is negative (stream is exhausted). + auto data = memory_map_.at(column); + out.assign(std::next(data, offset), std::next(data, allocated)); + return out; +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/primitives/manager.ipp b/include/bitcoin/database/impl/primitives/manager.ipp index 834b6a4e1..74d062caa 100644 --- a/include/bitcoin/database/impl/primitives/manager.ipp +++ b/include/bitcoin/database/impl/primitives/manager.ipp @@ -34,12 +34,22 @@ inline memory_ptr CLASS::get() const NOEXCEPT return files_.get_at(Column); } +TEMPLATE +template +inline memory CLASS::get1() const NOEXCEPT +{ + if constexpr (is_one(columns)) + return files_.get1(); + else + return files_.get_at1(Column); +} + TEMPLATE template inline memory_ptr CLASS::get(const Link& link) const NOEXCEPT { if (link.is_terminal()) - return nullptr; + return {}; const auto position = link_to_position(link); @@ -50,12 +60,28 @@ inline memory_ptr CLASS::get(const Link& link) const NOEXCEPT return files_.get_at(Column, position); } +TEMPLATE +template +inline memory CLASS::get1(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_.get1(position); + else + return files_.get_at1(Column, position); +} + TEMPLATE template > inline memory_ptr CLASS::get_capacity(const Link& link) const NOEXCEPT { if (link.is_terminal()) - return nullptr; + return {}; return files_.get_capacity(link_to_position(link)); } diff --git a/include/bitcoin/database/impl/primitives/nomaps.ipp b/include/bitcoin/database/impl/primitives/nomaps.ipp index a649e461a..c0678d885 100644 --- a/include/bitcoin/database/impl/primitives/nomaps.ipp +++ b/include/bitcoin/database/impl/primitives/nomaps.ipp @@ -130,16 +130,15 @@ code CLASS::reload() NOEXCEPT TEMPLATE template -memory_ptr CLASS::get_memory() const NOEXCEPT +memory CLASS::get_memory() const NOEXCEPT { - return manager_.template get(); + return manager_.template get1(); } // static TEMPLATE template -bool CLASS::get(const memory_ptr& ptr, const Link& link, - Element& element) NOEXCEPT +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT { static_assert(Element::size == width, "element size != width"); using namespace system; @@ -150,12 +149,12 @@ bool CLASS::get(const memory_ptr& ptr, const Link& link, if (is_limited(start)) return false; - const auto size = ptr->size(); + 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); + const auto offset = ptr.offset(start); if (is_null(offset)) return false; @@ -178,20 +177,20 @@ template bool CLASS::put(const Link& link, const Element& element) NOEXCEPT { using namespace system; - const auto ptr = manager_.template get(link); + const auto ptr = manager_.template get1(link); return put(ptr, element); } TEMPLATE template -bool CLASS::put(const memory_ptr& ptr, const Element& element) NOEXCEPT +bool CLASS::put(const memory& ptr, const Element& element) NOEXCEPT { static_assert(Element::size == width, "element size != width"); using namespace system; if (!ptr) return false; - iostream stream{ *ptr }; + iostream stream{ ptr }; flipper sink{ stream }; BC_DEBUG_ONLY(sink.set_limit(width * element.count());) diff --git a/include/bitcoin/database/impl/query/batch/ecdsa.ipp b/include/bitcoin/database/impl/query/batch/ecdsa.ipp index d4d0f3087..8a525d787 100644 --- a/include/bitcoin/database/impl/query/batch/ecdsa.ipp +++ b/include/bitcoin/database/impl/query/batch/ecdsa.ipp @@ -41,10 +41,10 @@ bool CLASS::verify_ecdsa_signatures(const stopper& cancel, using signature_t = const table::ecdsa_signature::span; using namespace system; - const auto correlate = pointer_cast(correlate_ptr->data()); - const auto digest = pointer_cast(digest_ptr->data()); - const auto compressed = pointer_cast(compressed_ptr->data()); - const auto signature = pointer_cast(signature_ptr->data()); + const auto correlate = pointer_cast(correlate_ptr.data()); + const auto digest = pointer_cast(digest_ptr.data()); + const auto compressed = pointer_cast(compressed_ptr.data()); + const auto signature = pointer_cast(signature_ptr.data()); // Shortest column. const auto count = store_.ecdsa.count(); diff --git a/include/bitcoin/database/impl/query/batch/schnorr.ipp b/include/bitcoin/database/impl/query/batch/schnorr.ipp index 6916cf47c..6ad317a9c 100644 --- a/include/bitcoin/database/impl/query/batch/schnorr.ipp +++ b/include/bitcoin/database/impl/query/batch/schnorr.ipp @@ -40,10 +40,10 @@ bool CLASS::verify_schnorr_signatures(const stopper& cancel, using signature_t = const table::schnorr_signature::span; using namespace system; - const auto correlate = pointer_cast(correlate_ptr->data()); - const auto digest = pointer_cast(digest_ptr->data()); - const auto xonly = pointer_cast(xonly_ptr->data()); - const auto signature = pointer_cast(signature_ptr->data()); + const auto correlate = pointer_cast(correlate_ptr.data()); + const auto digest = pointer_cast(digest_ptr.data()); + const auto xonly = pointer_cast(xonly_ptr.data()); + const auto signature = pointer_cast(signature_ptr.data()); // Shortest column. const auto count = store_.schnorr.count(); diff --git a/include/bitcoin/database/impl/query/batch/silent.ipp b/include/bitcoin/database/impl/query/batch/silent.ipp index 3813db19e..b80531782 100644 --- a/include/bitcoin/database/impl/query/batch/silent.ipp +++ b/include/bitcoin/database/impl/query/batch/silent.ipp @@ -40,9 +40,9 @@ bool CLASS::scan_silent(const stopper& cancel, const ec_secret& scan_key, using compressed_t = const table::silent_compressed::span; using namespace system; - const auto correlate = pointer_cast(correlate_ptr->data()); - const auto prefix = pointer_cast(prefix_ptr->data()); - const auto compressed = pointer_cast(compressed_ptr->data()); + const auto correlate = pointer_cast(correlate_ptr.data()); + const auto prefix = pointer_cast(prefix_ptr.data()); + const auto compressed = pointer_cast(compressed_ptr.data()); // Shortest column. const auto count = store_.silent.count(); diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index 76782c9d9..c5380b0c7 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -108,6 +108,15 @@ class storage /// Pointer is constrained to starting write within logical allocation. virtual memory_ptr get_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 get1(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_at1(size_t column, + size_t offset=zero) const NOEXCEPT = 0; }; } // namespace database diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index 8c37ceb9a..e6532d0fa 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -148,6 +148,12 @@ class mmap memory_ptr get_at(size_t column, size_t offset=zero) const NOEXCEPT override; + /// Remap-protected r/w access to start/offset (or null), within logical. + memory get1(size_t offset=zero) const NOEXCEPT override; + + /// Same as get() but within specified column (or null for invalid column). + memory get_at1(size_t column, size_t offset=zero) const NOEXCEPT override; + protected: template static constexpr size_t to_width(size_t offset) NOEXCEPT diff --git a/include/bitcoin/database/primitives/column.hpp b/include/bitcoin/database/primitives/column.hpp index 09b6e777f..748c53987 100644 --- a/include/bitcoin/database/primitives/column.hpp +++ b/include/bitcoin/database/primitives/column.hpp @@ -20,6 +20,7 @@ #define LIBBITCOIN_DATABASE_PRIMITIVES_COLUMN_HPP #include +#include namespace libbitcoin { namespace database { @@ -36,7 +37,7 @@ class column { } - INLINE memory_ptr get_memory() const NOEXCEPT + INLINE memory get_memory() const NOEXCEPT { return table_.template get_memory(); } @@ -48,7 +49,7 @@ class column } template - static INLINE bool get(const memory_ptr& ptr, const link& record, + static INLINE bool get(const memory& ptr, const link& record, Element& element) NOEXCEPT { return Table::template get(ptr, record, element); @@ -61,7 +62,7 @@ class column } template - INLINE bool put(const memory_ptr& ptr, const Element& element) NOEXCEPT + INLINE bool put(const memory& ptr, const Element& element) NOEXCEPT { return table_.template put(ptr, element); } diff --git a/include/bitcoin/database/primitives/manager.hpp b/include/bitcoin/database/primitives/manager.hpp index 12da24fc1..f5adce901 100644 --- a/include/bitcoin/database/primitives/manager.hpp +++ b/include/bitcoin/database/primitives/manager.hpp @@ -45,11 +45,15 @@ class managers /// Return memory object for column full map (null only if oom or unloaded). template inline memory_ptr get() const NOEXCEPT; + template + inline memory get1() const NOEXCEPT; - /// Return memory object for column record at position (null possible). + /// Return memory object for column record (null only if oom or unloaded). /// Pointer is constrained to starting write within logical allocation. template inline memory_ptr get(const Link& link) const NOEXCEPT; + template + inline memory get1(const Link& link) const NOEXCEPT; /// Return memory object (limited to AoS) within capacity. template = true> diff --git a/include/bitcoin/database/primitives/nomaps.hpp b/include/bitcoin/database/primitives/nomaps.hpp index 5690f8ce0..37d4e526c 100644 --- a/include/bitcoin/database/primitives/nomaps.hpp +++ b/include/bitcoin/database/primitives/nomaps.hpp @@ -74,11 +74,11 @@ class nomaps /// Column base ptr for batch processing (holds shared lock on body remap). template - memory_ptr get_memory() const NOEXCEPT; + memory get_memory() const NOEXCEPT; /// Get element from column at link using column base ptr (deserialize). template - static bool get(const memory_ptr& ptr, const Link& link, + static bool get(const memory& ptr, const Link& link, Element& element) NOEXCEPT; /// Get element from column at link. @@ -89,7 +89,7 @@ class nomaps template bool put(const Link& link, const Element& element) NOEXCEPT; template - bool put(const memory_ptr& ptr, const Element& element) NOEXCEPT; + bool put(const memory& ptr, const Element& element) NOEXCEPT; protected: using head = database::nohead; diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index df7e5f192..54d072ac4 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -290,6 +290,21 @@ class chunk_storages return ptr; } + memory get1(size_t offset=zero) const NOEXCEPT override + { + return get_at1(zero, offset); + } + + memory get_at1(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_{}; From aebc465b1efc9c84eca23fdfec8d3bb7c116cd6a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 18:40:50 -0400 Subject: [PATCH 4/8] Style. --- include/bitcoin/database/impl/query/batch/ecdsa.ipp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/database/impl/query/batch/ecdsa.ipp b/include/bitcoin/database/impl/query/batch/ecdsa.ipp index 8a525d787..c942ea1ac 100644 --- a/include/bitcoin/database/impl/query/batch/ecdsa.ipp +++ b/include/bitcoin/database/impl/query/batch/ecdsa.ipp @@ -116,15 +116,15 @@ bool CLASS::set_signatures(const hash_digest& digest, using compressed_t = table::ecdsa_compressed::put_refs; using signature_t = table::ecdsa_signature::put_refs; + using namespace system; const auto csigs = sigs.size(); const auto ckeys = keys.size(); - const auto count = system::chain::multisig::rows(csigs, ckeys); + const auto count = chain::multisig::rows(csigs, ckeys); // Caller must guard reads, this is writing into hot storage. // ======================================================================== const auto scope = get_transactor(); - using namespace system; const auto rows = possible_narrow_cast(count); // Allocate rows across all columns. From f893d4930dc98814b9e82602c1615b702acd09fa Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 20:04:24 -0400 Subject: [PATCH 5/8] Aggregate batch column writes under one remap guard. --- .../database/impl/memory/mmap_dispatch.ipp | 10 +++++++ .../database/impl/primitives/manager.ipp | 16 ++++++++++++ .../database/impl/primitives/nomaps.ipp | 26 ++++++++++++------- .../database/impl/query/batch/ecdsa.ipp | 8 ++++-- .../database/impl/query/batch/schnorr.ipp | 7 ++++- .../database/impl/query/batch/silent.ipp | 4 ++- .../database/memory/interfaces/storage.hpp | 5 ++++ include/bitcoin/database/memory/mmap.hpp | 4 +++ .../bitcoin/database/primitives/manager.hpp | 2 ++ .../bitcoin/database/primitives/nomaps.hpp | 10 ++++--- test/mocks/chunk_storage.hpp | 6 +++++ 11 files changed, 82 insertions(+), 16 deletions(-) diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp index 6bd33d17c..004d9a7b1 100644 --- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp +++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp @@ -93,6 +93,16 @@ memory_ptr CLASS::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT return get(offset); } +TEMPLATE +memory::iterator CLASS::get_at_raw(size_t column, size_t offset) const NOEXCEPT +{ + // get_raw not used for variably-sized heads, so should always be bounded. + BC_ASSERT(offset < (size() * widths.at(column))); + + // Pointer otherwise unguarded, not remap safe (use for nopmaps columns). + return std::next(memory_map_.at(column), offset); +} + TEMPLATE memory_ptr CLASS::get(size_t offset) const NOEXCEPT { diff --git a/include/bitcoin/database/impl/primitives/manager.ipp b/include/bitcoin/database/impl/primitives/manager.ipp index 74d062caa..a1a0b0cba 100644 --- a/include/bitcoin/database/impl/primitives/manager.ipp +++ b/include/bitcoin/database/impl/primitives/manager.ipp @@ -76,6 +76,22 @@ inline memory CLASS::get1(const Link& link) const NOEXCEPT return files_.get_at1(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_at_raw(Column, position); +} + TEMPLATE template > inline memory_ptr CLASS::get_capacity(const Link& link) const NOEXCEPT diff --git a/include/bitcoin/database/impl/primitives/nomaps.ipp b/include/bitcoin/database/impl/primitives/nomaps.ipp index c0678d885..39f59c2c9 100644 --- a/include/bitcoin/database/impl/primitives/nomaps.ipp +++ b/include/bitcoin/database/impl/primitives/nomaps.ipp @@ -128,6 +128,12 @@ code CLASS::reload() NOEXCEPT // query interface // ---------------------------------------------------------------------------- +TEMPLATE +memory CLASS::guard() const NOEXCEPT +{ + return get_memory(); +} + TEMPLATE template memory CLASS::get_memory() const NOEXCEPT @@ -140,11 +146,11 @@ TEMPLATE template bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT { - static_assert(Element::size == width, "element size != width"); - using namespace system; + 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; @@ -165,6 +171,7 @@ bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT 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 @@ -172,25 +179,26 @@ 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 { - using namespace system; - const auto ptr = manager_.template get1(link); + const auto ptr = manager_.template get_raw1(link); return put(ptr, element); } +// protected (unguarded memory access) TEMPLATE template -bool CLASS::put(const memory& ptr, const Element& element) NOEXCEPT +bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT { - static_assert(Element::size == width, "element size != width"); - using namespace system; - if (!ptr) + static_assert(Element::size == width); + if (is_null(it)) return false; - iostream stream{ ptr }; + using namespace system; + iostream stream{ it, system::maximum }; flipper sink{ stream }; BC_DEBUG_ONLY(sink.set_limit(width * element.count());) diff --git a/include/bitcoin/database/impl/query/batch/ecdsa.ipp b/include/bitcoin/database/impl/query/batch/ecdsa.ipp index c942ea1ac..be2f522fb 100644 --- a/include/bitcoin/database/impl/query/batch/ecdsa.ipp +++ b/include/bitcoin/database/impl/query/batch/ecdsa.ipp @@ -91,11 +91,13 @@ bool CLASS::set_signature(const hash_digest& digest, const auto row = possible_narrow_cast(one); // Allocate 1 row across all columns. - // TODO: this could provide a single remap lock for all puts below. const auto fk = store_.ecdsa.allocate(row); if (fk.is_terminal()) return false; + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.ecdsa.guard(); + // Write one value to each column in corresponding positions. return store_.ecdsa.correlate.put(fk, correlate_t{ {}, link, id }) && @@ -128,11 +130,13 @@ bool CLASS::set_signatures(const hash_digest& digest, const auto rows = possible_narrow_cast(count); // Allocate rows across all columns. - // TODO: this could provide a single remap lock for all puts below. const auto fk = store_.ecdsa.allocate(rows); if (fk.is_terminal()) return false; + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.ecdsa.guard(); + // Write values to each column in corresponding positions. return store_.ecdsa.correlate.put(fk, correlate_t{ {}, count, link, ckeys, csigs, id }) && diff --git a/include/bitcoin/database/impl/query/batch/schnorr.ipp b/include/bitcoin/database/impl/query/batch/schnorr.ipp index 6ad317a9c..d8189585a 100644 --- a/include/bitcoin/database/impl/query/batch/schnorr.ipp +++ b/include/bitcoin/database/impl/query/batch/schnorr.ipp @@ -89,11 +89,13 @@ bool CLASS::set_signature(const hash_digest& digest, const ec_xonly& point, const auto row = possible_narrow_cast(one); // Allocate 1 row across all columns. - // TODO: this could provide a single remap lock for all puts below. const auto fk = store_.schnorr.allocate(row); if (fk.is_terminal()) return false; + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.schnorr.guard(); + // Write one value to each column in corresponding positions. return store_.schnorr.correlate.put(fk, correlate_t{ {}, link }) && @@ -131,6 +133,9 @@ bool CLASS::set_signature(const schnorr_link& schnorr_fk, // ======================================================================== const auto scope = get_transactor(); + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.schnorr.guard(); + return store_.schnorr.correlate.put(schnorr_fk, correlate_t{ {}, link }) && store_.schnorr.digest.put(schnorr_fk, digest_t{ {}, digest }) && diff --git a/include/bitcoin/database/impl/query/batch/silent.ipp b/include/bitcoin/database/impl/query/batch/silent.ipp index b80531782..a87fd98ca 100644 --- a/include/bitcoin/database/impl/query/batch/silent.ipp +++ b/include/bitcoin/database/impl/query/batch/silent.ipp @@ -125,11 +125,13 @@ bool CLASS::set_silent(const tx_link& link, auto rows = possible_narrow_cast(prefixes.size()); // Allocate rows across all columns. - // TODO: this could provide a single remap lock for all puts below. const auto fk = store_.silent.allocate(rows); if (fk.is_terminal()) return false; + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.silent.guard(); + // Write values to each column in corresponding positions. return store_.silent.correlate.put(fk, correlate_t{ {}, rows, link }) && diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index c5380b0c7..d266c6995 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -96,6 +96,11 @@ class storage /// 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_at_raw(size_t column, + size_t offset=zero) const NOEXCEPT = 0; + /// Get remap-protected r/w access to offset (or null) allocated to size. virtual memory_ptr set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT = 0; diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index e6532d0fa..291972b72 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -137,6 +137,10 @@ class mmap /// Unprotected r/w access to start/offset (or null), within logical. memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override; + /// Unprotected r/w access to start/offset (or null), within logical. + memory::iterator get_at_raw(size_t column, + size_t offset=zero) const NOEXCEPT override; + /// Remap-protected r/w access to offset (or null) allocated to size. memory_ptr set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT override; diff --git a/include/bitcoin/database/primitives/manager.hpp b/include/bitcoin/database/primitives/manager.hpp index f5adce901..6f9cbbeb0 100644 --- a/include/bitcoin/database/primitives/manager.hpp +++ b/include/bitcoin/database/primitives/manager.hpp @@ -54,6 +54,8 @@ class managers inline memory_ptr get(const Link& link) const NOEXCEPT; template inline memory get1(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> diff --git a/include/bitcoin/database/primitives/nomaps.hpp b/include/bitcoin/database/primitives/nomaps.hpp index 37d4e526c..f68b2b25f 100644 --- a/include/bitcoin/database/primitives/nomaps.hpp +++ b/include/bitcoin/database/primitives/nomaps.hpp @@ -72,6 +72,9 @@ class nomaps /// Query interface (columnar). /// ----------------------------------------------------------------------- + /// Must hold as remap guard across puts(). + memory guard() const NOEXCEPT; + /// Column base ptr for batch processing (holds shared lock on body remap). template memory get_memory() const NOEXCEPT; @@ -85,17 +88,18 @@ class nomaps template bool get(const Link& link, Element& element) const NOEXCEPT; - /// Put previously allocated element to column at link. + /// Put previously allocated element to column at link (guard required). template bool put(const Link& link, const Element& element) NOEXCEPT; - template - bool put(const memory& ptr, const Element& element) NOEXCEPT; protected: using head = database::nohead; using body = database::managers, Columns::width...>; + template + bool put(memory::iterator it, const Element& element) NOEXCEPT; + // Thread safe (index/top/push). // Not thread safe (create/open/close/backup/restore). head head_; diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index 54d072ac4..b67c89944 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -275,6 +275,12 @@ class chunk_storages return get(offset); } + memory::iterator get_at_raw(size_t column, + size_t offset=zero) const NOEXCEPT override + { + return std::next(at(column).data(), offset); + } + memory_ptr get(size_t offset=zero) const NOEXCEPT override { return get_at(zero, offset); From ca697d3bfe27554cb4a2645685f23a7929b74de0 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 19:32:39 -0400 Subject: [PATCH 6/8] Style, sort methods. --- .../database/impl/memory/mmap_dispatch.ipp | 48 +++++++++---------- .../database/memory/interfaces/storage.hpp | 8 ++-- include/bitcoin/database/memory/mmap.hpp | 8 ++-- test/mocks/chunk_storage.hpp | 28 +++++------ 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp index 004d9a7b1..3048c9836 100644 --- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp +++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp @@ -33,30 +33,6 @@ namespace database { // Dispatch. // ---------------------------------------------------------------------------- -TEMPLATE -memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT -{ - const auto allocated = to_width(capacity()); - - const auto ptr = std::make_shared(remap_mutex_); - if (!loaded_ || is_null(ptr)) - return {}; - - auto data = memory_map_.front(); - ptr->assign(std::next(data, offset), std::next(data, allocated)); - return ptr; -} - -TEMPLATE -memory::iterator CLASS::get_raw(size_t offset) const NOEXCEPT -{ - // Pointer otherwise unguarded, not remap safe (use for fixed table heads). - if (offset > to_width(size())) - return {}; - - return std::next(memory_map_.front(), offset); -} - TEMPLATE memory_ptr CLASS::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT { @@ -93,6 +69,30 @@ memory_ptr CLASS::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT return get(offset); } +TEMPLATE +memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT +{ + const auto allocated = to_width(capacity()); + + const auto ptr = std::make_shared(remap_mutex_); + if (!loaded_ || is_null(ptr)) + return {}; + + auto data = memory_map_.front(); + ptr->assign(std::next(data, offset), std::next(data, allocated)); + return ptr; +} + +TEMPLATE +memory::iterator CLASS::get_raw(size_t offset) const NOEXCEPT +{ + // get_raw not used for variably-sized heads, so should always be bounded. + BC_ASSERT(offset < to_width(size())); + + // Pointer otherwise unguarded, not remap safe (use for fixed table heads). + return std::next(memory_map_.front(), offset); +} + TEMPLATE memory::iterator CLASS::get_at_raw(size_t column, size_t offset) const NOEXCEPT { diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index d266c6995..7c7343dad 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -88,6 +88,10 @@ class storage /// 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_ptr set(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_ptr get_capacity(size_t offset=zero) const NOEXCEPT = 0; @@ -101,10 +105,6 @@ class storage virtual memory::iterator get_at_raw(size_t column, size_t offset=zero) const NOEXCEPT = 0; - /// Get remap-protected r/w access to offset (or null) allocated to size. - virtual memory_ptr set(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 logical allocation. virtual memory_ptr get(size_t offset=zero) const NOEXCEPT = 0; diff --git a/include/bitcoin/database/memory/mmap.hpp b/include/bitcoin/database/memory/mmap.hpp index 291972b72..b78d6ec33 100644 --- a/include/bitcoin/database/memory/mmap.hpp +++ b/include/bitcoin/database/memory/mmap.hpp @@ -131,6 +131,10 @@ class mmap /// Increase logical by specified rows/bytes, return row of first (or eof). size_t allocate(size_t count) NOEXCEPT override; + /// Remap-protected r/w access to offset (or null) allocated to size. + memory_ptr set(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT override; + /// Remap-protected r/w access to start/offset (or null), within capacity. memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT override; @@ -141,10 +145,6 @@ class mmap memory::iterator get_at_raw(size_t column, size_t offset=zero) const NOEXCEPT override; - /// Remap-protected r/w access to offset (or null) allocated to size. - memory_ptr set(size_t offset, size_t size, - uint8_t backfill) NOEXCEPT override; - /// Remap-protected r/w access to start/offset (or null), within logical. memory_ptr get(size_t offset=zero) const NOEXCEPT override; diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index b67c89944..55a97ce59 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -240,20 +240,6 @@ class chunk_storages // access // ------------------------------------------------------------------------ - memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT override - { - using namespace system; - auto& buffer = at(zero); - const auto ptr = emplace_shared(map_mutex_); - ptr->assign(get_raw(offset), std::next(buffer.data(), buffer.size())); - return ptr; - } - - memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override - { - return std::next(at(zero).data(), offset); - } - memory_ptr set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT override { { @@ -275,6 +261,20 @@ class chunk_storages return get(offset); } + memory_ptr get_capacity(size_t offset=zero) const NOEXCEPT override + { + using namespace system; + auto& buffer = at(zero); + const auto ptr = emplace_shared(map_mutex_); + ptr->assign(get_raw(offset), std::next(buffer.data(), buffer.size())); + return ptr; + } + + memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override + { + return std::next(at(zero).data(), offset); + } + memory::iterator get_at_raw(size_t column, size_t offset=zero) const NOEXCEPT override { From cdd18fba63b8d9c51359fa75bef317c81cd3908b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 20:57:39 -0400 Subject: [PATCH 7/8] DRY. --- include/bitcoin/database/impl/memory/mmap_dispatch.ipp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp index 3048c9836..f2ca4b458 100644 --- a/include/bitcoin/database/impl/memory/mmap_dispatch.ipp +++ b/include/bitcoin/database/impl/memory/mmap_dispatch.ipp @@ -86,11 +86,8 @@ memory_ptr CLASS::get_capacity(size_t offset) const NOEXCEPT TEMPLATE memory::iterator CLASS::get_raw(size_t offset) const NOEXCEPT { - // get_raw not used for variably-sized heads, so should always be bounded. - BC_ASSERT(offset < to_width(size())); - // Pointer otherwise unguarded, not remap safe (use for fixed table heads). - return std::next(memory_map_.front(), offset); + return get_at_raw(zero, offset); } TEMPLATE From e8b0b3a6da4f6d6731c0694931dbba0573cdd472 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 13 Jul 2026 20:57:51 -0400 Subject: [PATCH 8/8] Comments. --- include/bitcoin/database/impl/query/batch/silent.ipp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/database/impl/query/batch/silent.ipp b/include/bitcoin/database/impl/query/batch/silent.ipp index a87fd98ca..36bab992c 100644 --- a/include/bitcoin/database/impl/query/batch/silent.ipp +++ b/include/bitcoin/database/impl/query/batch/silent.ipp @@ -117,7 +117,10 @@ bool CLASS::set_silent(const tx_link& link, using prefix_t = table::silent_prefix::put_ref; using compressed_t = table::silent_compressed::put_ref; - // TODO: caller must guard reads, this is writing into hot storage. + // TODO: Caller must guard reads, this is writing into hot storage. This + // TODO: requires caller to chase writers and account for the last contig- + // TODO: uously populated row (for searching) and to update subscriptions + // TODO: with additional scans as this position increases. // ======================================================================== const auto scope = get_transactor();