Skip to content

Commit fbbcdd1

Browse files
committed
reset a child's status when its halt() throws in ControlNode::haltChild
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.
1 parent a9d6e56 commit fbbcdd1

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

‎src/control_node.cpp‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,16 @@ void ControlNode::haltChild(size_t i)
5757
auto* child = children_nodes_[i];
5858
if(child->status() == NodeStatus::RUNNING)
5959
{
60-
child->haltNode();
60+
try
61+
{
62+
child->haltNode();
63+
}
64+
catch(...)
65+
{
66+
// Don't leave the child RUNNING, or the next reset would halt it again.
67+
child->resetStatus();
68+
throw;
69+
}
6170
}
6271
child->resetStatus();
6372
}

‎tests/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ set(BT_TESTS
4949
gtest_switch.cpp
5050
gtest_tree.cpp
5151
gtest_try_catch.cpp
52+
gtest_halt_child.cpp
5253
gtest_exception_tracking.cpp
5354
gtest_updates.cpp
5455
gtest_wakeup.cpp

‎tests/gtest_halt_child.cpp‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include "behaviortree_cpp/bt_factory.h"
2+
#include "behaviortree_cpp/control_node.h"
3+
4+
#include <stdexcept>
5+
6+
#include <gtest/gtest.h>
7+
8+
using BT::NodeStatus;
9+
10+
namespace
11+
{
12+
// RUNNING until halted, and its onHalted() throws. Counts the halts.
13+
class ThrowOnHalt : public BT::StatefulActionNode
14+
{
15+
public:
16+
ThrowOnHalt(const std::string& name, const BT::NodeConfig& config, int* halts)
17+
: StatefulActionNode(name, config), halts_(halts)
18+
{}
19+
static BT::PortsList providedPorts()
20+
{
21+
return {};
22+
}
23+
NodeStatus onStart() override
24+
{
25+
return NodeStatus::RUNNING;
26+
}
27+
NodeStatus onRunning() override
28+
{
29+
return NodeStatus::RUNNING;
30+
}
31+
void onHalted() override
32+
{
33+
(*halts_)++;
34+
throw std::runtime_error("halt failed");
35+
}
36+
37+
private:
38+
int* halts_;
39+
};
40+
41+
// Ticks its only child and halts it on request through haltChild().
42+
class HaltsChild : public BT::ControlNode
43+
{
44+
public:
45+
HaltsChild(const std::string& name, const BT::NodeConfig& config)
46+
: ControlNode(name, config)
47+
{}
48+
static BT::PortsList providedPorts()
49+
{
50+
return {};
51+
}
52+
NodeStatus tick() override
53+
{
54+
setStatus(NodeStatus::RUNNING);
55+
return children_nodes_[0]->executeTick();
56+
}
57+
void halt() override
58+
{
59+
haltChild(0);
60+
resetStatus();
61+
}
62+
};
63+
} // namespace
64+
65+
// A child whose halt() throws must not stay RUNNING, or the next haltChild()
66+
// halts it again and throws a second time.
67+
TEST(ControlNodeHaltChild, ThrowingHaltIsNotRepeated)
68+
{
69+
int halts = 0;
70+
BT::BehaviorTreeFactory factory;
71+
factory.registerNodeType<ThrowOnHalt>("ThrowOnHalt", &halts);
72+
factory.registerNodeType<HaltsChild>("HaltsChild");
73+
auto tree = factory.createTreeFromText(R"(
74+
<root BTCPP_format="4"><BehaviorTree>
75+
<HaltsChild><ThrowOnHalt/></HaltsChild>
76+
</BehaviorTree></root>)");
77+
78+
EXPECT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
79+
auto* node = static_cast<HaltsChild*>(tree.rootNode());
80+
EXPECT_THROW(node->halt(), std::runtime_error);
81+
EXPECT_EQ(halts, 1);
82+
EXPECT_NO_THROW(node->halt());
83+
EXPECT_EQ(halts, 1);
84+
EXPECT_EQ(tree.subtrees[0]->nodes[1]->status(), NodeStatus::IDLE);
85+
}

0 commit comments

Comments
 (0)