Skip to content

fix(backend): survive merge parents deleted from the unmerged graph#155

Draft
harelb wants to merge 1 commit into
MIT-SPARK:developfrom
harelb:fix/merge-tracker-deleted-parents
Draft

fix(backend): survive merge parents deleted from the unmerged graph#155
harelb wants to merge 1 commit into
MIT-SPARK:developfrom
harelb:fix/merge-tracker-deleted-parents

Conversation

@harelb

@harelb harelb commented Jul 8, 2026

Copy link
Copy Markdown

Problem

MergeTracker prunes merge-set children that no longer exist in the unmerged graph, but never checks the parent. The frontend can delete nodes (e.g. GraphUpdater delete updates when an object track is retracted), so a previously-recorded merge parent can vanish from the unmerged graph. Both applyMerges and updateAllMergeAttributes then call the merge hook, which typically starts with getNode(nodes.front()) on the unmerged graph:

terminate called after throwing an instance of 'std::out_of_range'
  what():  missing node 'O(15)'

We hit this live ~18 s after a loop closure (the LC triggers updateAllMergeAttributes over all recorded merge sets, including stale ones).

Fix

  • Drop a merge set whose parent is missing from either the unmerged or the merged graph (it can never be rebuilt again), in both entry points.
  • Null-guard the hook's return before setNodeAttributes so a hook may decline gracefully.

Includes a regression test (tests/backend/test_merge_tracker.cpp) exercising both entry points with a deleted parent plus the null-attrs path.

Related to #154 only circumstantially (both found in the same profiling session); no code overlap.

The frontend can delete nodes (e.g. GraphUpdater delete updates for
retracted object tracks), so a merge parent recorded by MergeTracker
can vanish from the unmerged graph. Both applyMerges and
updateAllMergeAttributes assumed parents persist and called the merge
hook, which typically does getNode(nodes.front()) on the unmerged graph
-> std::out_of_range -> std::terminate in the backend thread. Drop
merge sets whose parent left either graph and skip installing null
attributes returned by a hook.

@nathanhhughes nathanhhughes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! Would like to point out though that this probably was introduced one of the other claude changes (active nodes cannot be parents in a merge because the frontend can delete them whereas the frontend can't delete archived nodes)

MergeTracker tracker;
const MergeTracker::MergeFunc null_attrs = [](const DynamicSceneGraph&,
const std::vector<NodeId>&) {
return NodeAttributes::Ptr(nullptr);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly; I'd use auto and at least not pass in nullptr to the constructor (though I'm pretty sure you can also specify the return type of the lambda as NodeAttributes::Ptr and return nullptr instead which is my preference)

Comment on lines +117 to +118
// the frontend can delete nodes, so the recorded parent may be gone from either
// graph; the merged attributes can never be rebuilt again in that case

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop uninformative comment

const auto parent = iter->first;
// the frontend can delete nodes, so the recorded parent may be gone from either
// graph; the merged attributes can never be rebuilt again in that case
if (!unmerged.hasNode(parent) || !merged.hasNode(parent)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay to just check if unmerged doesn't have the parent; bad things have happened if the unmerged graph has the parent of a merge group and merged does not (which is fine to assert somewhere later)

Comment on lines +83 to +85
// the frontend can delete nodes, so the recorded parent may be gone from either
// graph; the merged attributes can never be rebuilt again in that case
if (!unmerged.hasNode(node) || !graph.hasNode(node)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto for comment and for checking the merged graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants