Skip to content

Commit b0ae01d

Browse files
kineticsystemclaude
andcommitted
Halt the tree and complete the execution when a node throws
When a node threw an exception while the tree was ticked, execute() aborted the goal and returned at once: the tree was not halted, and onTreeExecutionCompleted() was not called. Nodes still running, e.g. an action whose goal was sent, were left running, and a server that cleans up or reports the end of a run in onTreeExecutionCompleted() never learned that the run had ended. Halt the tree and call onTreeExecutionCompleted(FAILURE, false) there too, as for every other end of a run, for any exception thrown once onTreeCreated() has returned: by a node while the tree is ticked, or by onLoopAfterTick() or onLoopFeedback(). The result also carries the FAILURE node status. An exception thrown before, e.g. by the creation of the tree, is handled as before. If onTreeExecutionCompleted() itself throws while the execution is stopped, it is not called a second time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6c6aa07 commit b0ae01d

3 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎behaviortree_ros2/include/behaviortree_ros2/tree_execution_server.hpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ class TreeExecutionServer
125125

126126
/**
127127
* @brief onTreeExecutionCompleted is a callback invoked after the tree execution is completed,
128-
* i.e. if it returned SUCCESS/FAILURE or if the action was cancelled by the Action Client.
128+
* i.e. if it returned SUCCESS/FAILURE, if the action was cancelled by the Action Client,
129+
* or if an exception was thrown after onTreeCreated(), e.g. by a node while the tree was
130+
* ticked or by another callback: the tree is then halted, and the status is FAILURE.
129131
*
130132
* @param status The status of the tree after the last tick
131133
* @param was_cancelled True if the action was cancelled by the Action Client

‎behaviortree_ros2/src/tree_execution_server.cpp‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ void TreeExecutionServer::execute(
186186
executeRegistration();
187187
}
188188

189+
// Whether onTreeCreated() was called for this goal: only then is there a tree
190+
// of this goal to halt, and an execution to complete, if an exception is thrown.
191+
bool tree_created = false;
192+
// Whether onTreeExecutionCompleted() was called for this goal, so that it is
193+
// not called again if it throws.
194+
bool execution_completed = false;
195+
189196
// Loop until something happens with ROS or the node completes
190197
try
191198
{
@@ -198,6 +205,7 @@ void TreeExecutionServer::execute(
198205

199206
// call user defined function after the tree has been created
200207
onTreeCreated(p_->tree);
208+
tree_created = true;
201209
p_->groot_publisher.reset();
202210
p_->groot_publisher =
203211
std::make_shared<BT::Groot2Publisher>(p_->tree, p_->params.groot2_port);
@@ -209,10 +217,11 @@ void TreeExecutionServer::execute(
209217

210218
// operations to be done if the tree execution is aborted, either by
211219
// cancel requested or by onLoopAfterTick()
212-
auto stop_action = [this, &action_result](BT::NodeStatus status,
213-
const std::string& message) {
220+
auto stop_action = [this, &action_result, &execution_completed](
221+
BT::NodeStatus status, const std::string& message) {
214222
p_->tree.haltTree();
215223
action_result->node_status = ConvertNodeStatus(status);
224+
execution_completed = true;
216225
// override the message value if the user defined function returns it
217226
if(auto msg = onTreeExecutionCompleted(status, true))
218227
{
@@ -264,6 +273,30 @@ void TreeExecutionServer::execute(
264273
{
265274
action_result->return_message = std::string("Behavior Tree exception:") + ex.what();
266275
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
276+
status = BT::NodeStatus::FAILURE;
277+
action_result->node_status = ConvertNodeStatus(status);
278+
if(tree_created && !execution_completed)
279+
{
280+
// An exception thrown after onTreeCreated(), e.g. by a node while the tree
281+
// is ticked or by a user defined callback, leaves the nodes as they were,
282+
// e.g. an action whose goal still runs: halt them, as when the execution
283+
// is cancelled.
284+
try
285+
{
286+
p_->tree.haltTree();
287+
}
288+
catch(const std::exception& halt_ex)
289+
{
290+
RCLCPP_ERROR(kLogger, "Exception while halting the Behavior Tree: %s",
291+
halt_ex.what());
292+
}
293+
// The execution ends here too: let the user clean up, as for any other end.
294+
// Override the message value if the user defined function returns it
295+
if(auto msg = onTreeExecutionCompleted(status, false))
296+
{
297+
action_result->return_message = msg.value();
298+
}
299+
}
267300
goal_handle->abort(action_result);
268301
return;
269302
}

‎behaviortree_ros2/tree_execution_server.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ This happens if:
5353
1. Ticking the tree returns SUCCESS/FAILURE
5454
2. The `rclcpp_action::Client` cancels the action.
5555
3. Callback `onLoopAfterTick`cancels the execution.
56+
4. An exception is thrown after `onTreeCreated` returns, e.g. by a node while the
57+
tree is ticked, or by `onLoopAfterTick` or `onLoopFeedback`. The tree is halted,
58+
and the callback receives FAILURE. It is not called when the exception is thrown
59+
before, e.g. by the creation of the tree or by `onTreeCreated` itself, and it is
60+
not called a second time when it throws itself.
5661

5762
Argument `was_cancelled`is true in the 1st case, false otherwise.
5863

0 commit comments

Comments
 (0)