New documentation on how to take a message from a subscriber without executing a callback function

Hello everyone!

Autoware Documentation provides a guideline to take a message from Subscription without executing any callback function.

https://autowarefoundation.github.io/autoware-documentation/main/contributing/coding-guidelines/ros-nodes/topic-message-handling/

I believe that the document must be helpful for many users and projects even though it only explains the take() method defined in the rclcpp::Subscription class. The official rclcpp API document and ros2/examples modestly introduce the take() method, but there is less documentation that tells how to use the take() method.

In ROS based projects, it is often seen that a callback function is called when a topic based message is received by a subscriber. The programming manner seems to be common sense in the ROS community because it is explained in tutorials and ros2/demos.

Autoware developers also had this common sense but it was a burden for Autoware. Autoware is one of the large applications based on ROS 2. Autoware is composed of many nodes and has a large number of message communication for inter-process and intra-process communication. The large number of message communication implies too much execution of callback functions which wastes CPU resources. It is easy to understand that wasting CPU resources can be one of the causes of high CPU load of Autoware. Besides, a thread may not necessarily be assigned to a callback function even though it is ready to run if the system is too busy to allocate enough CPU time to ready tasks.

I think that overuse of callback functions can be detrimental to a large application like Autoware. I have suggested that Autoware should use the rclcpp:Subscriptionā€™s take() method instead of a callback function to reference a topic message. I think that the take() method will give us some advantages;

  • The take() method can reduce the number of subscription callback function calls
  • There is no need to take a topic message from a subscription that is not consumed in the user logic
  • There is no mandatory thread waking for the callback function which bothers programmers; thread forces them to worry about parallelism and data race

We believe that the take() method can be helpful for many users and projects. If you want to utilize ROS 2 and want more details than the tutorial, please visit the following page.

Topic message handling guideline - Autoware Documentation

4 Likes

Thanks for the interesting read. Itā€™s really a pity the user has to distinguish intra and inter-process comms, as that is something he doesnā€™t necessarily control.

Exactly! I was liking the initial read. For nodes that (have to) run timer based, or conditionally, this could be an enhancement.

But now we also have to take extra care how the node is being setup. Thatā€™s a bummer :confused:. Would be nice if this could be generalized so it works for inter- and intra-process communication.

Thanks for sharing this @takamine. At the ROSConā€™21 executor workshop, I had a talk in which I tried to shed some light on the mechanisms behind the APIs and the reasons why we decided to build our executor on top of the rclcpp waitset and the PollingSubscription. We had essentially the same reasons that you describe. Maybe this is interesting in this context.

Slides
Video

3 Likes

This is an interesting write-up. Iā€™ve actually been thinking about this recently, specifically with respect to intra-process communication. One option could be to extend the take() method to have the ability to pull from the intra-process buffer if data is available. A potential issue with this is the prioritization of intra or inter process messages. At least with the current waitset implementation in the executors, callbacks for messages from both sources will be interleaved.

I would like to mention that thereā€™s a mistake in the following example from the article:

  SteeringReport::SharedPtr msg;
  rclcpp::MessageInfo msg_info;
  if (sub_->take(msg, msg_info)) {

The take() method has a signature of:

  bool
  take(ROSMessageType & message_out, rclcpp::MessageInfo & message_info_out)

So the example would need to be updated to:

  SteeringReport msg;
  rclcpp::MessageInfo msg_info;
  if (sub_->take(msg, msg_info)) {
1 Like

@peci1 and @Timple
Thanks for visiting.

I was also disappointed with the asymmetric API design. I looked into the implementation of intra-process communication, and I found that 1-to-N intra-process communication seemed to be the cause of the asymmetric design.
The data structure of the return value of take_data() is complicated due to 1-to-N intra-process communication.

@michael-poehnl
After writing our document, @ZhenshengLee let us know about your great work.

I should have noticed your slides earlier because we have been dealing with a lot of callback function and thread for a long time.

I think your approach, the PollingSubscription, looks good because programmers donā€™t have to be concerned about a callback function that the create_subscription() provided by rclcpp always requires.
I was frustrated by the API specification of create_subscription() before I figured out how to suppress the callback execution.

1 Like

@thomasmoore-torc
Thank you for feedback. I fixed the document according to your advice.

The difference of design between intra- and inter- process communication seems controversial for utilizing rclcpp efficiently.