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

Make nodes, publishers, subscriptions, services, clients, action servers, action clients Python context managers #1280

Closed
clalancette opened this issue May 7, 2024 · 1 comment · Fixed by #1291, #1293 or #1294
Labels

Comments

@clalancette
Copy link
Contributor

clalancette commented May 7, 2024

Let's say that you want to create an rclpy node with a publisher and a subscription. In order to do this entirely correctly today, you would have to do the following:

rclpy.init()
try:
  node = rclpy.create_node('minimal_action_server')
  try:
    publisher = node.create_publisher(String, 'chatter', 1)
    try:
      subscription = node.create_subscription(String, 'chatter', lambda msg: print(msg), 1)
      try:
        do_thing()
      finally:
        node.destroy_subscription(subscription)
    finally:
      node.destroy_publisher(publisher)
  finally:
    node.destroy_node()
finally:
  rclpy.try_shutdown()

That is, every time we successfully create an entity, we really need to put a try..finally block around it to clean up only those parts that we've initialized so far. This is very unwieldy, so we don't actually recommend it in our examples or documentation.

However, we should be able to do better. In particular, if all of the initialization, nodes, publishers, subscriptions, services, clients, action servers, and action clients were Python context managers (i.e. they all implemented __enter__ and __exit__), this could be cleaned up to something like:

with rclpy.init() as init:
  with rclpy.create_node('minimal_action_server') as node:
    with node.create_publisher(String, 'chatter', 1) as pub:
      with node.create_subscription(String, 'chatter', lambda msg: print(msg), 1) as subscription:
        do_thing()

Or even better, using a contextlib.ExitStack:

with ExitStack() as stack:
    stack.enter_context(rclpy.init())
    node = stack.enter_context(rclpy.create_node('minimal_action_server'))
    publisher = stack.enter_context(node.create_publisher(String, 'chatter', 1))
    subscription = stack.enter_context(node.create_subscription(String, 'chatter', lambda msg: print(msg), 1))
    do_thing()

This came out of discussion around ros2/examples#379

@sloretz sloretz assigned clalancette and unassigned clalancette May 16, 2024
@clalancette clalancette reopened this Jun 3, 2024
@clalancette clalancette reopened this Jun 4, 2024
@clalancette clalancette reopened this Jun 4, 2024
@clalancette
Copy link
Contributor Author

While there are still a few outstanding pull requests for this, this has largely been completed in Rolling. Thus I'm going to close this one out. Thanks in particular to @sloretz for all of the reviews in getting this in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
2 participants