Repository: BehaviorTree/BehaviorTree.CPP (C++)

Issue:
Using a ReactiveSequence with asynch condition
TLDR: don't use an asynch action for the condition in a ReactiveSequence.

I have a simple ReactiveSequence like this. It's very similar to the example [here](https://www.behaviortree.dev/docs/tutorial-basics/tutorial_04_sequence):

```
    <!-- Keep running DummyStatefulAction as long as the monitors are good -->
    <ReactiveSequence>
        <CheckJointVelocityMonitor1/>  <!-- Inherits from BT::RosServiceNode -->
        <DummyStatefulAction/>            <!-- A long-running asynch action -->
    </ReactiveSequence>
```

The prints when this BT is executing are:

```
CheckJointVelocityMonitor1 returning SUCCESS  // This BT node always returns success
Starting DummyStatefulAction                                // This is expected, it comes from .onStart(), which returns BT::NodeStatus::RUNNING
DummyStatefulAction halted!                                  // I don't understand where the halt comes from
CheckJointVelocityMonitor1 returning SUCCESS
Starting DummyStatefulAction
DummyStatefulAction halted!
CheckJointVelocityMonitor1 returning SUCCESS
Starting DummyStatefulAction
DummyStatefulAction halted!
CheckJointVelocityMonitor1 returning SUCCESS
Starting DummyStatefulAction
DummyStatefulAction halted!
  ... loop continues
```

`CheckJointVelocityMonitor1` always returns SUCCESS. I've verified with print statements. `DummyStatefulAction` appears to be ticked once and returns RUNNING, then it gets halted. I don't understand why it gets halted. (Edit: it gets halted because it returns RUNNING.)

Here's the declaration for `DummyStatefulAction`, which inherits from `BT::StatefulActionNode`.

```
class DummyStatefulAction : public BT::StatefulActionNode
{
public:
  DummyStatefulAction(const std::string& name, const BT::NodeConfiguration& config) : StatefulActionNode(name, config)
  {
  }

  static BT::PortsList providedPorts();

  BT::NodeStatus onStart() override;

  BT::NodeStatus onRunning() override;

  void onHalted() override;
};
```

Here's the definition for `DummyStatefulAction`. It only returns RUNNING, never SUCCESS or FAILURE.

```
BT::NodeStatus DummyStatefulAction::onStart()
{
  std::cerr << "Starting DummyStatefulAction" << std::endl;
  return BT::NodeStatus::RUNNING;
}

BT::NodeStatus DummyStatefulAction::onRunning()
{
  std::cerr << "Running DummyStatefulAction" << std::endl;
  return BT::NodeStatus::RUNNING;
}

void DummyStatefulAction::onHalted()
{
  std::cerr << "DummyStatefulAction halted!" << std::endl;
}
```


Discussion:
I think it must be something related to `<CheckJointVelocityMonitor1/>` since, if I repalce it with `<AlwaysSuccess/>`, everything is fine. Like this:

```
    <ReactiveSequence>
        <!-- CheckJointVelocityMonitor1/ -->
        <AlwaysSuccess/>
        <DummyStatefulAction/>
    </ReactiveSequence>
```

CheckJointVelocityMonitor1 inherits from `BT::RosServiceNode`:

```
class TriggerService : public BT::RosServiceNode<std_srvs::srv::Trigger>
{
public:
  TriggerService() = delete;

  explicit TriggerService(const std::string& name, const BT::NodeConfig& conf, const BT::RosNodeParams& params)
    : RosServiceNode<Trigger>(name, conf, params)
  {
  }

  static BT::PortsList providedPorts()
  {
    return providedBasicPorts({});
  }

  bool setRequest(Trigger::Request::SharedPtr& request) override;

  BT::NodeStatus onResponseReceived(const Trigger::Response::SharedPtr& response) override;

  BT::NodeStatus onFailure(BT::ServiceNodeErrorCode error) override;
};
```

I'll work on getting a minimal example.
With some more print statement debugging, I see the trouble is that the RosServiceNode is getting halted with every other call to `tick()`?!

```
response received: 1
Starting DummyStatefulAction
Ticking RosServiceNode
DummyStatefulAction halted!
Ticking RosServiceNode
response received: 1
Starting DummyStatefulAction
Ticking RosServiceNode
DummyStatefulAction halted!
Ticking RosServiceNode
response received: 1
Starting DummyStatefulAction
Ticking RosServiceNode
DummyStatefulAction halted!
Ticking RosServiceNode
response received: 1
Starting DummyStatefulAction
```
Well, I can achieve the result I want by hacking the source code of `bt_service_node.hpp` to block until the service returns, within the `tick()` function.

It would be nice if the BT could block while the service node is running but I don't see a way to do that.
I can make a PR to check the node is synchronous during parsing, if you want. Or maybe just add a parameter to make `BT::RosServiceNode` block. Or is there an easier way?
You know what, it doesn't matter for my immediate application. I'll use a topic listener instead and check the timestamp of the latest message. Still curious if there was a better way to do this, though.
> With some more print statement debugging, I see the trouble is that the RosServiceNode is getting halted with every other call to `tick()`?!通过进一步调试打印语句，我发现问题出在每调用一次`tick() 时`，RosServiceNode 就会停止一次！
> 
> ```
> response received: 1
> Starting DummyStatefulAction
> Ticking RosServiceNode
> DummyStatefulAction halted!
> Ticking RosServiceNode
> response received: 1
> Starting DummyStatefulAction
> Ticking RosServiceNode
> DummyStatefulAction halted!
> Ticking RosServiceNode
> response received: 1
> Starting DummyStatefulAction
> Ticking RosServiceNode
> DummyStatefulAction halted!
> Ticking RosServiceNode
> response received: 1
> Starting DummyStatefulAction
> ```

Yes, in **ReactiveSequence**, when a child node of the current tick returns **RUNNING**, it halt the other child nodes

https://github.com/BehaviorTree/BehaviorTree.CPP/blob/1fcb624d4d7b9d1f357378b20ff19bdcc3853cea/src/controls/reactive_sequence.cpp#L45-L53
Yeah, the root cause was using an asynch node for the condition. It was dumb now that I think about it, but also not really clear in the docs.



Patch:
diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp
index 32a1e42ab..31e2f28f7 100644
--- a/src/xml_parsing.cpp
+++ b/src/xml_parsing.cpp
@@ -541,6 +541,29 @@ void VerifyXML(const std::string& xml_text,
           ThrowError(line_number,
                      std::string("The node <") + name + "> must have 1 or more children");
         }
+        if(name == "ReactiveSequence")
+        {
+          size_t async_count = 0;
+          for(auto child = node->FirstChildElement(); child != nullptr;
+              child = child->NextSiblingElement())
+          {
+            const std::string child_name = node->FirstChildElement()->Name();
+            const auto child_search = registered_nodes.find(child_name);
+            const auto child_type = child_search->second;
+            if(child_type == NodeType::CONTROL &&
+               ((child_name == "ThreadedAction") ||
+                (child_name == "StatefulActionNode") ||
+                (child_name == "CoroActionNode") || (child_name == "AsyncSequence")))
+            {
+              ++async_count;
+              if(async_count > 1)
+              {
+                ThrowError(line_number, std::string("A ReactiveSequence cannot have more "
+                                                    "than one async child."));
+              }
+            }
+          }
+        }
       }
     }
     //recursion


Test patch:
diff --git a/tests/gtest_reactive.cpp b/tests/gtest_reactive.cpp
index b393c03e8..47e4435ae 100644
--- a/tests/gtest_reactive.cpp
+++ b/tests/gtest_reactive.cpp
@@ -156,3 +156,31 @@ TEST(Reactive, TestLogging)
   ASSERT_EQ(observer.getStatistics("testA").success_count, num_ticks);
   ASSERT_EQ(observer.getStatistics("success").success_count, num_ticks);
 }
+
+TEST(Reactive, TwoAsyncNodesInReactiveSequence)
+{
+  static const char* reactive_xml_text = R"(
+<root BTCPP_format="4" >
+  <BehaviorTree ID="MainTree">
+    <ReactiveSequence>
+      <AsyncSequence name="first">
+        <TestA/>
+        <TestB/>
+        <TestC/>
+      </AsyncSequence>
+      <AsyncSequence name="second">
+        <TestD/>
+        <TestE/>
+        <TestF/>
+      </AsyncSequence>
+    </ReactiveSequence>
+  </BehaviorTree>
+</root>
+)";
+
+  BT::BehaviorTreeFactory factory;
+  std::array<int, 6> counters;
+  RegisterTestTick(factory, "Test", counters);
+
+  EXPECT_ANY_THROW(auto tree = factory.createTreeFromText(reactive_xml_text));
+}