rclpy.spin_until_future_complete() removes node from executor added by rclpy.spin()

asked 2022-03-31 10:18:43 -0600

bjoernolav gravatar image

I'm writing a ROS2 rclpy node (using Galactic and Ubuntu 20.04LTS) that is executed using rclpy.spin, but also uses arclpy.spin_until_future_complete for waiting for a TF transform to become available. I'm having trouble with the node stopping execution, due to the rclpy.spin_until_future_complete unregistering the node in the SingleThreadedExecutor, making it stop.

I'm not sure if this is intentional, or if I'm doing something unappropriate here. Initially I tried just looking up the transform using lookup_transform with a timeout, but this blocks the executor in whole making the TF transform callbacks not being served - therefore I'm attempting to use the wait_for_transform_async function instead.

I'm getting this to work by replacing the rclpy.spin_until_future_complete() with my own using rclpy.spin_once(), but that seems like an unintended workaround.

MWE:

import rclpy
from rclpy.time import Time
from rclpy.node import Node
import geometry_msgs.msg as geomsgs
import tf2_ros


class Blah(Node):
    def __init__(self):
        super().__init__('blahblah')

        # Set up tf
        self.__tf_buffer = tf2_ros.Buffer()
        self.__tf_listener = tf2_ros.TransformListener(self.__tf_buffer, self)

        self.__timer = self.create_timer(
            1., self.run_once)

    def run_once(self):
        self.get_logger().info('Running, list of nodes in executor: {}'.format(self.executor.get_nodes()))

        # Look up transform
        self._lookup_transform('frame1', 'frame2')

    def _lookup_transform(self, target_frame: str, source_frame: str) -> geomsgs.TransformStamped:
        # Wait for transform using future
        t_trans = Time(seconds=0)
        transform_available_future = self.__tf_buffer.wait_for_transform_async(
            target_frame, source_frame, t_trans)

        self.get_logger().info('Executor nodes before spin_until_future_complete: {}'.format(self.executor.get_nodes()))
        rclpy.spin_until_future_complete(
            self, transform_available_future, timeout_sec=1.0)
        self.get_logger().info('Executor nodes after spin_until_future_complete: {}'.format(self.executor.get_nodes()))

        # Get transform
        try:
            trans = self.__tf_buffer.lookup_transform(
                target_frame, source_frame, t_trans)
        except tf2_ros.TransformException as e:
            self.get_logger().fatal('TF lookup exception {}'.format(e))
            raise e
        return trans


def main():
    rclpy.init()
    node = Blah()
    rclpy.spin(node)
    rclpy.shutdown()


if __name__ == "__main__":
    main()

Running the node, I get the following output:

[INFO] [1648739520.263017100] [blahblah]: Running, list of nodes in executor: [<package.blah.Blah object at 0x7fc77e2c6a90>]
[INFO] [1648739520.263507200] [blahblah]: Executor nodes before spin_until_future_complete: [<package.blah.Blah object at 0x7fc77e2c6a90>]
[INFO] [1648739520.264210100] [blahblah]: Executor nodes after spin_until_future_complete: []
edit retag flag offensive close merge delete

Comments

Were you able to solve it?

märve gravatar image märve  ( 2022-07-06 08:19:38 -0600 )edit

Not really. I made it work using rclpy.spin_once()instead. It's probably better to use a MultiThreadedExecutor instead, and let the thread block until the transform becomes available. The TF listener uses a seaparate callback group (https://github.com/ros2/geometry2/blo...) - so using this approach will make the TF callback being handled by a separate thread.

bjoernolav gravatar image bjoernolav  ( 2022-07-12 15:06:12 -0600 )edit

I'm assuming there is no new news on this? When you say you were able to use rclpy.spin_once(), I'm assuming it was in the main loop and not used instead of rclpy.spin_until_future_complete()? Something like?:

def main():
    rclpy.init()
    node = MyNode()
    while True:
        rclpy.spin_once(node)
    node.destroy()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

I'm encountering this issue on Humble and the above seems to work as it constantly re-registers my node to spin after the internal call removes the node from the spin()tasks. As this behavior isn't called out in the examples (https://docs.ros.org/en/humble/Tutori...) the need to do this really feels like a bug.

How might one use MultiThreadedExecutor to solve this?

Doom4535 gravatar image Doom4535  ( 2023-01-10 18:24:37 -0600 )edit

Running into a very similar problem. A callback containing spin_until_future_complete( ) which executes fine the first time, but then stops responding to any future service calls after its first callback returns. Putting in a fixed 10s wait stops the problem so the culprit is spin_until_future_complete, but this "workaround" is far far from ideal.

SMCC94 gravatar image SMCC94  ( 2023-03-30 04:49:04 -0600 )edit