Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor GraphCache to support multiple types for the same topic. #70

Merged
merged 13 commits into from
Dec 2, 2023

Conversation

Yadunund
Copy link
Member

@Yadunund Yadunund commented Nov 24, 2023

This PR

  • Updates the GraphCache implementation to support multiple types over the same topic name.
  • Previously we were not keeping track of multiple pub/subs in the same node that publish over the same topic and let alone with different topic types. This PR addresses both these problems as well.
  • It also reduces code duplication when adding/removing pub/subs by relying on lambdas that are reused for both pub/subs.
  • Adds data structures and methods (see liveliness_utils.hpp) to manage token generation and conversion to C++ types. No longer have any magic string literal comparisons or custom key generation for unordered_maps.

@gavanderhoorn
Copy link
Contributor

gavanderhoorn commented Nov 24, 2023

This PR updates the GraphCache implementation to support multiple types over the same topic name.

I'm just watching this repository, so likely out-of-the-loop a bit, but what would be a use-case for this? Wouldn't that break the "strong typing" of ROS topics?

@clalancette
Copy link
Collaborator

I'm just watching this repository, so likely out-of-the-loop a bit, but what would be a use-case for this? Wouldn't that break the "strong typing" of ROS topics?

This is a supported use-case today:

Terminal 1:

$ ros2 topic pub -r 1 /foo std_msgs/msg/String '{data: hello}'

Terminal 2:

$ ros2 topic pub -r 1 /foo std_msgs/msg/Int32 '{data: 3}'

Terminal 3:

$ ros2 topic list -t
/foo [std_msgs/msg/Int32, std_msgs/msg/String]
/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]

Is it a good idea? No. But since the aim is to keep most of the semantics the same with rmw_zenoh_cpp, we should support it.

@gavanderhoorn
Copy link
Contributor

gavanderhoorn commented Nov 27, 2023

This is a supported use-case today

yes, I know ..

I still can't really come up with a good use-case for this though.

Is it a good idea? No.

Indeed.

@Yadunund
Copy link
Member Author

Yadunund commented Nov 27, 2023

I'm just watching this repository, so likely out-of-the-loop a bit, but what would be a use-case for this? Wouldn't that break the "strong typing" of ROS topics?

This is a supported use-case today:

Terminal 1:

$ ros2 topic pub -r 1 /foo std_msgs/msg/String '{data: hello}'

Terminal 2:

$ ros2 topic pub -r 1 /foo std_msgs/msg/Int32 '{data: 3}'

Terminal 3:

$ ros2 topic list -t
/foo [std_msgs/msg/Int32, std_msgs/msg/String]
/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]

Is it a good idea? No. But since the aim is to keep most of the semantics the same with rmw_zenoh_cpp, we should support it.

@clalancette this should work with PR now. Could you give it a spin?
Edit: ros2 topic list -v should also be supported now

Yadunund and others added 5 commits November 27, 2023 23:47
Signed-off-by: Chris Lalancette <[email protected]>
Also fix a couple of bugs where we were mixing up subscriptions
and publications, leading to incorrect counts.

Signed-off-by: Chris Lalancette <[email protected]>
Because it returned a reference, it is difficult to
tell the lifetime of it.  It would also be hard to add
in locking in the future.  Since we are reaching through
it in most cases, just return some data directly.

Signed-off-by: Chris Lalancette <[email protected]>
@clalancette clalancette force-pushed the yadu/support_multiple_topic_types branch from ae0b4d9 to 178cc38 Compare November 27, 2023 18:10
Signed-off-by: Chris Lalancette <[email protected]>
Copy link
Collaborator

@clalancette clalancette left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this is looking pretty good.

Inline, I've left one major piece of feedback about APIs.

Besides that, I'm noticing segmentation faults and errors when I shut nodes down with this. I suspect that we are trying to process some liveliness tokens while we are shutting down the context, but I'm not sure. I think we should probably address that before merging this in.

Comment on lines 290 to 293
const std::optional<TopicInfo> & Entity::topic_info() const
{
return this->topic_info_;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of APIs like this, that return a reference to something inside of the class.

There are two (related) reasons:

  1. It becomes difficult to tell what the lifetime of this returned value is. If the caller took a reference, but then this class gets destroyed, it is now holding onto a dangling reference.
  2. If we want to add in locking around topic_info_, for instance, it becomes tricky; we have to expose the locking mechanism outside of the class to all callers, and then all callers have to remember to actually lock it.

You can see that I actually changed node_info above to three separate APIs that just return the needed data without references. That is much safer, in my opinion, and I think we should do a similar thing for topic_info(), type(), and keyexpr() in this PR.

Copy link
Member Author

@Yadunund Yadunund Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I've pushed 4488b97. I hope it's fine to let topic_info()still return an std::optional<TopicInfo>. Else if we split into 3 APIs, we will need to check all 3 params are set each time we want to parse necessary data for pub/subs. To prevent large copies of strings each time, I've updated the implementation to make one copy of topic_info() and access the required fields where ever possible.

Besides that, I'm noticing segmentation faults and errors when I shut nodes down with this. I suspect that we are trying to process some liveliness tokens while we are shutting down the context, but I'm not sure. I think we should probably address that before merging this in.

Do you have any commands to reproduce this behavior? I wasn't able to do so. But I did encounter this problem early on when I had the ros2 daemon running from an older commit of this branch. After ros2 daemon stop and rebuilding I never saw that again.

Copy link
Collaborator

@francocipollone francocipollone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff! 🚀

I left some minor comments

I tested locally running demo nodes:

(docker) franco@:~/ros_dev_ws$ ros2 topic list -v
Published topics:
 * /chatter [std_msgs/msg/String] 1 publisher
 * /parameter_events [rcl_interfaces/msg/ParameterEvent] 3 publishers
 * /rosout [rcl_interfaces/msg/Log] 3 publishers

Subscribed topics:
 * /chatter [std_msgs/msg/String] 1 subscriber
 * /parameter_events [rcl_interfaces/msg/ParameterEvent] 2 subscribers

I haven't experienced any error when shutting down the nodes

Comment on lines +58 to +62
std::optional<liveliness::Entity> valid_entity = liveliness::Entity::make(keyexpr);
if (!valid_entity.has_value()) {
// Error message has already been logged.
return;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding error handling.

When the entity can't be created, the error msg is set and then it is returned. However, when the method (parse_put) is being called there is no way to know it failed from the caller's perspective.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be argued how to deal with errors with discovery and whether that should throw and exception or continue the process if the data transport is still working. Maybe something we can iterate on as things get clearer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened a ticket to work on in the next iteration #72

"rmw_zenoh_cpp",
"Received liveliness token for non-node entity without required parameters.");
return std::nullopt;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Just for consistency, should we check here if we have strictly more than 8 parts? We shouldn't reach that part though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be in favor of not having any magic numbers like 8 anywhere in the code. Maybe we can update the Entity API to return the number of parts for a given EntityType.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ticketed to work on in the next iteration #71

if (ns_it == graph_.end()) {
NodeMap node_map = {
{entity.node_name(), make_graph_node(entity)}};
graph_.insert(std::make_pair(entity.node_namespace(), std::move(node_map)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: using emplace instead of insert might be useful to not rely on the compiler for optimization in the construction/copy of the object

Emplace creates the object in-place instead of moving it with insert if possible.

https://en.cppreference.com/w/cpp/container/unordered_map/emplace

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 357f658

rmw_zenoh_cpp/src/detail/graph_cache.cpp Show resolved Hide resolved
Comment on lines 118 to 124
auto topic_data_ptr = std::make_shared<TopicData>(
topic_info,
TopicStats{pub_count, sub_count}
);
graph_topics_[topic_info.name_] = GraphNode::TopicDataMap{
{topic_info.type_, topic_data_ptr}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the same as topic_data_map?

Suggested change
auto topic_data_ptr = std::make_shared<TopicData>(
topic_info,
TopicStats{pub_count, sub_count}
);
graph_topics_[topic_info.name_] = GraphNode::TopicDataMap{
{topic_info.type_, topic_data_ptr}
};
graph_topics_[topic_info.name_] = topic_data_map;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 357f658

NamespaceMap graph_ = {};

// Optimize topic lookups across the graph.
GraphNode::TopicMap graph_topics_ = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for documentation's sake. Without this optimized collection for topic lookups, does the time for the queries(for topics) go up considerably? Just wondering why solving the query on demand isn't a chance. Taking into account that keeping the double source of truth might be error-prone.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the cache, lookups to check if a certain topic exists in the graph have O(1) complexity on average. Else if we have to iterate through graph_cache_, it will be O(n*n'*n'') where we have to iterate through n namepaces, n' nodes within each namespace and potentially n'' topics per node.

Signed-off-by: Yadunund <[email protected]>
@Yadunund
Copy link
Member Author

Yadunund commented Dec 2, 2023

Thanks for the review @clalancette and @francocipollone! I've addressed the blocking feedback and opened tickets for the others. Will merge this in now to continue impl of introspection methods.

@Yadunund Yadunund merged commit 1bfc2c1 into rolling Dec 2, 2023
5 checks passed
@delete-merged-branch delete-merged-branch bot deleted the yadu/support_multiple_topic_types branch December 2, 2023 00:31
Yadunund added a commit that referenced this pull request Jan 12, 2024
* use std::find_if to find matching pubs

Signed-off-by: Yadunund <[email protected]>

* Refactor to support multiple types

Signed-off-by: Yadunund <[email protected]>

* Refactor liveliness token generation

Signed-off-by: Yadunund <[email protected]>

* Map topic types to TopicData

Signed-off-by: Yadunund <[email protected]>

* Store map of types in graph_topics_

Signed-off-by: Yadunund <[email protected]>

* fix bug in deleting and counting topics

Signed-off-by: Yadunund <[email protected]>

* Cleanup liveliness_utils

Signed-off-by: Yadunund <[email protected]>

* Style cleanups.

Signed-off-by: Chris Lalancette <[email protected]>

* Cleanup some of the code in the liveliness.

Also fix a couple of bugs where we were mixing up subscriptions
and publications, leading to incorrect counts.

Signed-off-by: Chris Lalancette <[email protected]>

* Remove Entity::node_info.

Because it returned a reference, it is difficult to
tell the lifetime of it.  It would also be hard to add
in locking in the future.  Since we are reaching through
it in most cases, just return some data directly.

Signed-off-by: Chris Lalancette <[email protected]>

* Remove some uses of auto.

Signed-off-by: Chris Lalancette <[email protected]>

* Return copies from Entity

Signed-off-by: Yadunund <[email protected]>

* Address feedback

Signed-off-by: Yadunund <[email protected]>

---------

Signed-off-by: Yadunund <[email protected]>
Signed-off-by: Yadunund <[email protected]>
Signed-off-by: Chris Lalancette <[email protected]>
Co-authored-by: Chris Lalancette <[email protected]>
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.

4 participants