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..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) { @@ -406,8 +404,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 +427,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 +443,42 @@ 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()); + // 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 acutal language references (excluding LangCache + TagIndex) + // 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 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. + // 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; + } + } m_inc_refed_tags.clear(); } @@ -482,9 +486,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