BehaviorTree/BehaviorTree.CPP

RetryNode not respecting ticking frequency

Closed this issue · 4 comments

Describe the bug

version: 4.6.2

In the BT below, I would expect AlwaysFailure to be ticked every 1000 ms however it's not the case, it's being ticked seemingly without any sleep. I suspect we are stuck in this loop but I'm not sure if this is intended or not

How to Reproduce*

#include "behaviortree_cpp/bt_factory.h"
#include <behaviortree_cpp/loggers/bt_cout_logger.h>
#include <iostream>

using namespace BT;

int main()
{
    BehaviorTreeFactory factory;

    const std::string xml_text = R"(
    <root BTCPP_format="4">
        <BehaviorTree ID="MainTree">
            <RetryUntilSuccessful num_attempts="10">
                <AlwaysFailure/>
            </RetryUntilSuccessful>
        </BehaviorTree>
    </root>
    )";

    auto tree = factory.createTreeFromText(xml_text);
    StdCoutLogger logger(tree);

    std::cout << "Running the behavior tree:\n";

    NodeStatus result = NodeStatus::RUNNING;
    while (result == NodeStatus::RUNNING)
    {
        result = tree.tickOnce();
        std::cout << "Tree Status: " << toStr(result) << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }

    std::cout << "Behavior tree finished with status: " 
              << toStr(result) << "\n";

    return 0;
}

Result:

Running the behavior tree:
[1732787639.935]: RetryUntilSuccessful      IDLE -> RUNNING
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: AlwaysFailure             IDLE -> FAILURE
[1732787639.935]: AlwaysFailure             FAILURE -> IDLE
[1732787639.935]: RetryUntilSuccessful      RUNNING -> FAILURE
[1732787639.935]: RetryUntilSuccessful      FAILURE -> IDLE
Tree Status: FAILURE
Behavior tree finished with status: FAILURE

Another interesting similar case:

I changed tickOnce to tickExactlyOnce to avoid getting into the loop mentioned above and it behaves as expected. However, if I change the std::this_thread::sleep_for to a tree.sleep as shown below we get a similar behavior as above in which AlwaysFailure is ticked without a sleep.

#include "behaviortree_cpp/bt_factory.h"
#include <behaviortree_cpp/loggers/bt_cout_logger.h>
#include <iostream>

using namespace BT;

int main()
{
    BehaviorTreeFactory factory;

    const std::string xml_text = R"(
    <root BTCPP_format="4">
        <BehaviorTree ID="MainTree">
            <RetryUntilSuccessful num_attempts="10">
                <AlwaysFailure/>
            </RetryUntilSuccessful>
        </BehaviorTree>
    </root>
    )";

    auto tree = factory.createTreeFromText(xml_text);
    StdCoutLogger logger(tree);

    std::cout << "Running the behavior tree:\n";

    NodeStatus result = NodeStatus::RUNNING;
    while (result == NodeStatus::RUNNING)
    {
        result = tree.tickExactlyOnce();
        std::cout << "Tree Status: " << toStr(result) << "\n";
        std::chrono::milliseconds duration(1000);
        tree.sleep(duration);
    }

    std::cout << "Behavior tree finished with status: " 
              << toStr(result) << "\n";

    return 0;
}
Running the behavior tree:
[1732788586.811]: RetryUntilSuccessful      IDLE -> RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
Tree Status: RUNNING
[1732788586.811]: AlwaysFailure             IDLE -> FAILURE
[1732788586.811]: AlwaysFailure             FAILURE -> IDLE
[1732788586.811]: RetryUntilSuccessful      RUNNING -> FAILURE
[1732788586.811]: RetryUntilSuccessful      FAILURE -> IDLE
Tree Status: FAILURE
Behavior tree finished with status: FAILURE

The interesting part is that here we get the print Tree Status: RUNNING meaning that it's the tree.sleep that gets preempted.
I think the bug originates from here, can you explain the logic behind calling emitWakeUpSignal before returning RUNNING?

@SteveMacenski just FYI if someone ever reports it in Nav2. I think it also affects the RecoveryNode (not 100% sure though)

I think it is related to this: #723
The behavior is the intended one, i.e., it does not wait a tick to retry.

ok, thanks for investigating this and explaining it to others that may have the same problem. closing the issue now