Synchronous request to service in callback results in deadlock

When somebody tries to call a service in a callback, for example:

# create a client that will call a service
client = node.create_client(SomeService, 'some_service')

# create subscription with a callback that calls a service
def callback(msg):
    req = Request()
    result = client.call(req)   # deadlocks here
subscription = node.create_subscription(String, 'cause_deadlock', callback, 10)

a deadlock immediately occurs. This is a very easy mistake to make. The only thing “preventing” this mistake from being made is a docstring in rclpy.

More should be done to prevent this, such as:

  1. Tutorials: The tutorials and examples only show the use of call_async(). If the synchronous call() API is to exist as a feature on the same tier as call_async(), there should be official tutorials and examples to warn against incorrect usage. Right now, the only resources online that are somewhat relevant to this topic are maybe some questions on ROS Answers (such as here and here). And/or
  2. Deprecate/Remove: rclcpp does not have an equivalent API for synchronously sending a request to a service. rclpy shouldn’t need one either. It feels like call() was introduced to workaround the inconvenience of how rclpy.task.Future doesn’t have a blocking get() similar to C++'s std::future.

Thoughts on this?

3 Likes

:+1: for updating tutorials

That’s not intentional, see:

I’d also propose that we support recursive spin, which will require work on our part, but could be possible, or as someone suggested on one of the issues, provide a styles like send_request(...).then([](response) {...});.

2 Likes

Also, here’s an issue proposing a synchronous method for rclcpp::Client: https://github.com/ros2/rclcpp/issues/975

+1 for this and for the action server (which… probably shouldn’t be used as frequently, but its an easier introduction into the concept)