Skip to content

reset a child's status when its halt() throws in ControlNode::haltChild - #1273

Merged
facontidavide merged 3 commits into
BehaviorTree:masterfrom
dvadovsz:fix/haltchild-reset-on-throw
Nov 7, 2026
Merged

facontidavide merged 3 commits into
BehaviorTree:masterfrom
dvadovsz:fix/haltchild-reset-on-throw

Conversation

@dvadovsz

Copy link
Copy Markdown
Contributor

Split out of #1229, as requested in review.

ControlNode::haltChild now resets the child's status before rethrowing an exception from the child's halt(). Previously the child stayed RUNNING, so the next halt of the parent halted it again and threw a second time. When that happened inside ~Tree(), the process terminated. Callers still see the exception. haltChildren() gets the fix through haltChild().

tests/gtest_halt_child.cpp adds a control node that halts its child through haltChild(), with a child whose onHalted() throws. Without the fix the second halt calls onHalted() again and throws, the child is left RUNNING, and the test binary aborts in std::terminate when the tree is destroyed. With the fix it passes.

ControlNode::resetChildren() and DecoratorNode::resetChild() have the same pattern. I left them out to keep this small and can add them here if you prefer.

API/ABI: no signature changes.

Previously the child stayed RUNNING, so the next halt of the parent halted
it again and threw a second time. When that happened inside ~Tree(), the
process terminated. Callers still see the exception.

This covers haltChild() and haltChildren(), which calls it.
ControlNode::resetChildren() and DecoratorNode::resetChild() have the same
pattern and are left for a follow-up.
facontidavide and others added 2 commits November 7, 2026 18:05
The previous commit reset a child's status in ControlNode::haltChild() when
its halt() threw, so the next reset does not halt it again. But built-in
nodes halt their children through ControlNode::resetChildren() and
DecoratorNode::resetChild()/haltChild(), and Tree::haltTree() halts the root
directly: all of them go through TreeNode::haltNode() and kept the bug (the
node stays RUNNING, is halted a second time, and ~Tree() -> haltTree() throws
again from a destructor, i.e. std::terminate).

Put the guard in the single choke point, TreeNode::haltNode():
try { halt(); } catch(...) { resetStatus(); throw; }, and drop the
now-redundant try/catch in ControlNode::haltChild(). The exception still
propagates. The existing _onHalted post-condition code is unchanged.

Add tests with built-in parents (Sequence, RetryUntilSuccessful): haltTree()
throws once, every node is IDLE afterwards, a second haltTree() does not
throw and onHalted() is not called again. No ABI change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@facontidavide

Copy link
Copy Markdown
Collaborator

Thanks @dvadovsz. The bug is real, but haltChild() is the less-used path. Built-in nodes halt their children through ControlNode::halt() → resetChildren() and through DecoratorNode::resetChild(), and Tree::haltTree() halts the root directly. All of them go through TreeNode::haltNode(), and they kept the bug.

I pushed b1a3f86 on top of a merge of master. It moves the guard to that single point (try { halt(); } catch(...) { resetStatus(); throw; }) and removes the now-redundant try/catch from haltChild(), so control_node.cpp is identical to master again. It adds tests with built-in parents (Sequence, RetryUntilSuccessful). With the guard reverted, all three tests fail: onHalted() runs twice and the process ends in std::terminate. Your test is kept. I also changed one static_cast to dynamic_cast for clang-tidy.

There's no ABI change; the edit is .cpp-only. One limit remains: the first throw inside ~Tree() still terminates, because the destructor is implicitly noexcept. That's tracked separately: #1281.

I'll squash-merge once CI is green. #1229 can then drop its copy of the control_node.cpp change.

@facontidavide
facontidavide merged commit b6709b6 into BehaviorTree:master Nov 7, 2026
17 checks passed
facontidavide added a commit to dvadovsz/BehaviorTree.CPP that referenced this pull request Nov 7, 2026
Take master's src/control_node.cpp and tests/gtest_halt_child.cpp (BehaviorTree#1273,
b6709b6), which supersede this branch's copy of the haltChild fix
(fbbcdd1): the status reset now lives in TreeNode::haltNode().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME1hnSFSzibU5phxboGsjk
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