From 246f32a98a181579ab871af2310fd9bad53c8469 Mon Sep 17 00:00:00 2001 From: Tomasz Wejroch Date: Tue, 25 Nov 2025 16:06:39 +0100 Subject: [PATCH] Added shared lifetime for GC0 global state to avoid crash on exit --- src/dbzero/workspace/GC0.cpp | 51 ++++++++++++++++++++++++------------ src/dbzero/workspace/GC0.hpp | 46 +++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/dbzero/workspace/GC0.cpp b/src/dbzero/workspace/GC0.cpp index 76534e40..43efdcc5 100644 --- a/src/dbzero/workspace/GC0.cpp +++ b/src/dbzero/workspace/GC0.cpp @@ -3,10 +3,12 @@ namespace db0 { - - std::vector GC0::m_ops; - std::unordered_map GC0::m_ops_map; - bool GC0::m_initialized = false; + std::shared_ptr& GC0::getGlobalSharedState() + { + // Enforce singleton-like behavior. Single initialization is guaranteed by C++. + static std::shared_ptr global_shared_state = std::make_shared(); + return global_shared_state; + } template void dropByAddr(Memspace &memspace, Address addr, const std::vector &ops) { @@ -15,13 +17,15 @@ namespace db0 } GC0::GC0(db0::swine_ptr &fixture) - : super_t(fixture) + : super_t(fixture) + , m_shared_state(getGlobalSharedState()) , m_read_only(false) { } GC0::GC0(db0::swine_ptr &fixture, Address address, bool read_only) : super_t(tag_from_address(), fixture, address) + , m_shared_state(getGlobalSharedState()) , m_read_only(read_only) { } @@ -29,6 +33,11 @@ namespace db0 GC0::~GC0() { } + + GC0_SharedState& GC0::getSharedState() + { + return *m_shared_state; + } bool GC0::tryRemove(void *vptr, bool is_volatile) { @@ -39,7 +48,7 @@ namespace db0 } NoArgsFunction drop_op = nullptr; - auto &ops = m_ops[it->second]; + auto &ops = getSharedState().m_ops[it->second]; // if type implements flush then remove it from flush map as well if (ops.flush) { m_flush_map.erase(vptr); @@ -80,8 +89,9 @@ namespace db0 void GC0::detachAll() { std::unique_lock lock(m_mutex); + auto &ops_list = getSharedState().m_ops; for (auto &vptr_item : m_vptr_map) { - m_ops[vptr_item.second].detach(vptr_item.first); + ops_list[vptr_item.second].detach(vptr_item.first); } } @@ -97,10 +107,11 @@ namespace db0 std::unique_lock lock(m_mutex); std::unordered_set addresses; std::size_t count = 0; + auto &ops_list = getSharedState().m_ops; for (auto vptr : vptrs) { auto it = m_vptr_map.find(vptr); if (it != m_vptr_map.end()) { - auto &ops = m_ops[it->second]; + auto &ops = ops_list[it->second]; ops.commit(vptr); if (ops.hasRefs && !ops.hasRefs(vptr)) { addresses.insert(toTypedAddress(ops.address(vptr))); @@ -126,8 +137,9 @@ namespace db0 void GC0::commitAll() { std::unique_lock lock(m_mutex); + auto &ops_list = getSharedState().m_ops; for (auto &vptr_item : m_vptr_map) { - m_ops[vptr_item.second].commit(vptr_item.first); + ops_list[vptr_item.second].commit(vptr_item.first); } } @@ -156,8 +168,9 @@ namespace db0 lock.unlock(); // call flush where it's provided + auto &ops_list = getSharedState().m_ops; for (auto &item : flush_ops) { - m_ops[item.second].flush(item.first, false); + ops_list[item.second].flush(item.first, false); } } @@ -171,20 +184,23 @@ namespace db0 if (!fixture) { THROWF(db0::InternalException) << "GC0::collect: cannot collect without a valid fixture"; } + + auto &ops_list = getSharedState().m_ops; + auto &ops_map = getSharedState().m_ops_map; // drop scheduled for deletion for (auto &addr_pair: m_scheduled_for_deletion) { - auto ops_id = m_ops_map[addr_pair.second]; - assert(ops_id < m_ops.size()); - m_ops[ops_id].dropByAddr(fixture, addr_pair.first.getAddress()); + auto ops_id = ops_map[addr_pair.second]; + assert(ops_id < ops_list.size()); + ops_list[ops_id].dropByAddr(fixture, addr_pair.first.getAddress()); } m_scheduled_for_deletion.clear(); for (auto addr: *this) { - auto ops_id = m_ops_map[addr.getType()]; - assert(ops_id < m_ops.size()); + auto ops_id = ops_map[addr.getType()]; + assert(ops_id < ops_list.size()); // object will be dropped only if it has no references - m_ops[ops_id].dropByAddr(fixture, addr.getAddress()); + ops_list[ops_id].dropByAddr(fixture, addr.getAddress()); } super_t::clear(); } @@ -213,8 +229,9 @@ namespace db0 } } // call reverse flush where it's provided (use revert=true) + auto &ops_list = getSharedState().m_ops; for (auto &item : m_flush_map) { - m_ops[item.second].flush(item.first, true); + ops_list[item.second].flush(item.first, true); } m_volatile.clear(); m_atomic = false; diff --git a/src/dbzero/workspace/GC0.hpp b/src/dbzero/workspace/GC0.hpp index 585972ad..d53ac492 100644 --- a/src/dbzero/workspace/GC0.hpp +++ b/src/dbzero/workspace/GC0.hpp @@ -57,6 +57,23 @@ namespace db0 return m_value; } }; + + struct GC0_SharedState + { + std::vector m_ops; + // GC-ops by storage class + std::unordered_map m_ops_map; + // flag indicating if static bindings were initialized + bool m_initialized; + + GC0_SharedState() = default; + // We don't want accidental copy + GC0_SharedState(const GC0_SharedState&) = delete; + GC0_SharedState(GC0_SharedState&&) = delete; + GC0_SharedState& operator=(const GC0_SharedState&) = delete; + GC0_SharedState& operator=(GC0_SharedState&) = delete; + }; + #define GC0_Declare protected: \ friend class db0::GC0; \ @@ -130,11 +147,11 @@ namespace db0 std::optional erase(void *vptr); private: - static std::vector m_ops; - // GC-ops by storage class - static std::unordered_map m_ops_map; - // flag indicating if static bindings were initialized - static bool m_initialized; + // Keep shared state 'alive' until it isn't needed anymore + std::shared_ptr m_shared_state; + static std::shared_ptr& getGlobalSharedState(); + GC0_SharedState& getSharedState(); + const bool m_read_only; // type / ops_id std::unordered_map m_vptr_map; @@ -153,9 +170,10 @@ namespace db0 template static void registerSingleType() { - T::m_gc_ops_id = GCOps_ID(m_ops.size()); - m_ops.push_back(T::getGC_Ops()); - m_ops_map[T::storageClass()] = T::m_gc_ops_id; + auto &state = getGlobalSharedState(); + T::m_gc_ops_id = GCOps_ID(state->m_ops.size()); + state->m_ops.push_back(T::getGC_Ops()); + state->m_ops_map[T::storageClass()] = T::m_gc_ops_id; } }; @@ -165,11 +183,12 @@ namespace db0 assert(vptr); std::unique_lock lock(m_mutex); // detach function must always be provided - assert(m_ops[T::m_gc_ops_id].detach); - assert(m_ops[T::m_gc_ops_id].address); + auto &ops_list = getSharedState().m_ops; + assert(ops_list[T::m_gc_ops_id].detach); + assert(ops_list[T::m_gc_ops_id].address); m_vptr_map[vptr] = T::m_gc_ops_id; // if the type implements flush then also add it to the flush map - if (m_ops[T::m_gc_ops_id].flush) { + if (ops_list[T::m_gc_ops_id].flush) { m_flush_map[vptr] = T::m_gc_ops_id; } if (m_atomic) { @@ -193,12 +212,13 @@ namespace db0 template void GC0::registerTypes() { - if (m_initialized) { + auto &state = getGlobalSharedState(); + if (state->m_initialized) { return; } (registerSingleType(), ...); - m_initialized = true; + state->m_initialized = true; } }