From da9d5d346eb91bfe51dd8c5553182f57a582bbe4 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Wed, 6 May 2026 15:22:18 +0200 Subject: [PATCH 1/3] fix(TagIndex): fixed problem with taging preinit objects --- python_tests/test_issues_15.py | 6 +++ .../collections/full_text/FT_BaseIndex.cpp | 15 +++++- src/dbzero/object_model/tags/TagIndex.cpp | 46 +++++++++---------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/python_tests/test_issues_15.py b/python_tests/test_issues_15.py index e691ac1a..ca7a54c2 100644 --- a/python_tests/test_issues_15.py +++ b/python_tests/test_issues_15.py @@ -57,3 +57,9 @@ def test_find_inside_memo_init_after_tagging_self(db0_rw_fixture): """db0.find() inside a memo __init__ must not segfault after db0.tags(self).add().""" obj = TaggerAndFinder() assert obj._results == [] + +def test_find_memo_init_after_tagging_in_init(db0_rw_fixture): + """db0.find() inside a memo __init__ must not segfault after db0.tags(self).add().""" + obj = TaggerAndFinder() + obj2 = TaggerAndFinder() + assert len(list(db0.find(TaggerAndFinder, "my-tag"))) == 2 \ No newline at end of file diff --git a/src/dbzero/core/collections/full_text/FT_BaseIndex.cpp b/src/dbzero/core/collections/full_text/FT_BaseIndex.cpp index c8a07606..082873d2 100644 --- a/src/dbzero/core/collections/full_text/FT_BaseIndex.cpp +++ b/src/dbzero/core/collections/full_text/FT_BaseIndex.cpp @@ -457,9 +457,20 @@ namespace db0 } } } - buf.clear(); + // Keep zero-addr refs (mid-init objects whose address isn't assigned yet) + // so they are available for the next flush once __init__ completes. + // Clear everything else: resolved refs, direct values, and the reverted set. + buf.m_values.clear(); + buf.m_reverted.clear(); + for (auto it = buf.m_value_refs.begin(); it != buf.m_value_refs.end(); ) { + if (is_valid(*it->second)) { + it = buf.m_value_refs.erase(it); + } else { + ++it; + } + } } - + template class FT_BaseIndex; template class FT_BaseIndex; diff --git a/src/dbzero/object_model/tags/TagIndex.cpp b/src/dbzero/object_model/tags/TagIndex.cpp index 8aa67cf2..316f8a64 100644 --- a/src/dbzero/object_model/tags/TagIndex.cpp +++ b/src/dbzero/object_model/tags/TagIndex.cpp @@ -406,8 +406,8 @@ namespace db0::object_model std::function remove_tag_callback = [&](UniqueAddress obj_addr) { auto it = m_object_cache.find(obj_addr); // object may not exist if tags are removed post-deletion - auto obj_ptr = it->second.get(); if (it != m_object_cache.end()) { + auto obj_ptr = it->second.get(); // NOTE: we check for acutal language references (excluding LangCache + TagIndex) if (LangToolkit::decRefMemo(true, obj_ptr) && !LangToolkit::hasAnyLangRefs(obj_ptr, 2)) { auto &memo = type_manager.extractAnyObject(obj_ptr); @@ -429,9 +429,8 @@ namespace db0::object_model // flush all short tags' updates if (!m_batch_op_short.assureEmpty()) { - m_batch_op_short->flush(&add_tag_callback, &remove_tag_callback, + m_batch_op_short->flush(&add_tag_callback, &remove_tag_callback, &add_index_callback, &erase_index_callback); - assert(m_batch_op_short.empty()); } std::function add_long_index_callback = [&](LongTagT long_tag_addr) { @@ -446,35 +445,34 @@ namespace db0::object_model // flush all long tags' updates if (!m_batch_op_long.assureEmpty()) { - m_batch_op_long->flush(&add_tag_callback, &remove_tag_callback, + m_batch_op_long->flush(&add_tag_callback, &remove_tag_callback, &add_long_index_callback, &erase_long_index_callback); - assert(m_batch_op_long->empty()); } if (!m_batch_op_types.assureEmpty()) { - // now, scan the object cache and revert any unreferenced objects (no dbzero refs, no lang refs) - assert(m_active_pre_cache.empty()); - for (const auto &item: m_object_cache) { - auto obj_ptr = item.second.get(); - auto &memo = type_manager.extractAnyObject(obj_ptr); - // NOTE: dropped instances should've already been reverted by now - // NOTE: we check for acutal language references (excluding LangCache + TagIndex) - if (!memo.isDropped() && !memo.hasAnyRefs() && !LangToolkit::hasAnyLangRefs(obj_ptr, 2)) { - m_batch_op_types->revert(memo.getUniqueAddress()); - } - } - // flush all type-tag updates if (!m_batch_op_types.assureEmpty()) { // NOTE: we don't pass any remove_tag_callback since type tags are only removed when objects are dropped m_batch_op_types->flush(&add_tag_callback, nullptr, &add_index_callback, &erase_index_callback); - assert(m_batch_op_types.empty()); } } - } - + } + m_object_cache.clear(); - m_active_cache.clear(); + // Keep mid-init entries (zero-addr placeholder) for the next flush cycle; + // erase only entries that have been resolved to a real address. + for (auto it = m_active_cache.begin(); it != m_active_cache.end(); ) { + if (it->second.isValid()) { + it = m_active_cache.erase(it); + } else { + ++it; + } + } + // Rebuild pre-cache so surviving mid-init objects stay alive until next flush. + m_active_pre_cache.clear(); + for (const auto &item : m_active_cache) { + m_active_pre_cache.insert(ObjectSharedExtPtr(item.first)); + } m_inc_refed_tags.clear(); } @@ -482,9 +480,9 @@ namespace db0::object_model { for (auto &item: m_active_cache) { auto &memo = LangToolkit::getTypeManager().extractAnyObject(item.first); - // NOTE: defunct objects have to be ignored since they don't have a valid address - // NOTE: defunct objects, since no valid unique address is assigned will be auto-reverted on flush - if (!memo.isDefunct()) { + // NOTE: defunct objects and mid-init objects (still in __init__, no address yet) + // must be skipped — their placeholder stays zero and is preserved for the next flush. + if (!memo.isDefunct() && memo.hasInstance()) { auto object_addr = memo.getUniqueAddress(); assert(object_addr.isValid()); // initialize active value with the actual object address From 55fe495c76007ca29fcd8292a6be8d7a0e464615 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Thu, 7 May 2026 11:34:25 +0200 Subject: [PATCH 2/3] fix(test): fixed problem in tests --- src/dbzero/object_model/tags/TagIndex.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dbzero/object_model/tags/TagIndex.cpp b/src/dbzero/object_model/tags/TagIndex.cpp index 316f8a64..f4d274bf 100644 --- a/src/dbzero/object_model/tags/TagIndex.cpp +++ b/src/dbzero/object_model/tags/TagIndex.cpp @@ -450,6 +450,17 @@ namespace db0::object_model } if (!m_batch_op_types.assureEmpty()) { + // Scan fully-initialized objects and revert type tags for those with no remaining refs. + // Mid-init objects are in m_active_pre_cache, not m_object_cache, so they are unaffected. + for (const auto &item: m_object_cache) { + auto obj_ptr = item.second.get(); + auto &memo = type_manager.extractAnyObject(obj_ptr); + // NOTE: dropped instances should've already been reverted by now + // NOTE: we check for actual language references (excluding LangCache + TagIndex) + if (!memo.isDropped() && !memo.hasAnyRefs() && !LangToolkit::hasAnyLangRefs(obj_ptr, 2)) { + m_batch_op_types->revert(memo.getUniqueAddress()); + } + } // flush all type-tag updates if (!m_batch_op_types.assureEmpty()) { // NOTE: we don't pass any remove_tag_callback since type tags are only removed when objects are dropped From 298d6154d9e2c144988044189ae6cce92ce0a9a3 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Thu, 7 May 2026 11:49:07 +0200 Subject: [PATCH 3/3] fix(tests): changed chace clear --- src/dbzero/object_model/tags/TagIndex.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/dbzero/object_model/tags/TagIndex.cpp b/src/dbzero/object_model/tags/TagIndex.cpp index f4d274bf..c1850611 100644 --- a/src/dbzero/object_model/tags/TagIndex.cpp +++ b/src/dbzero/object_model/tags/TagIndex.cpp @@ -374,8 +374,6 @@ namespace db0::object_model // this is to resolve addresses of incomplete objects (must be done before flushing) buildActiveValues(); - // the pre-cache can be cleared now (while active cache still needs to be preserved) - m_active_pre_cache.clear(); auto &type_manager = LangToolkit::getTypeManager(); // NOTE: some object might've been dropped in the meantime, need to be reverted from batch operations for (const auto &item: m_object_cache) { @@ -472,18 +470,15 @@ namespace db0::object_model m_object_cache.clear(); // Keep mid-init entries (zero-addr placeholder) for the next flush cycle; // erase only entries that have been resolved to a real address. + // Pre-cache ref was born in getBatchOperation; drop it here at resolution time. for (auto it = m_active_cache.begin(); it != m_active_cache.end(); ) { if (it->second.isValid()) { + m_active_pre_cache.erase(ObjectSharedExtPtr(it->first)); it = m_active_cache.erase(it); } else { ++it; } } - // Rebuild pre-cache so surviving mid-init objects stay alive until next flush. - m_active_pre_cache.clear(); - for (const auto &item : m_active_cache) { - m_active_pre_cache.insert(ObjectSharedExtPtr(item.first)); - } m_inc_refed_tags.clear(); }