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