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

Enable implicit ROS startup by launch_ros actions. #128

Merged
merged 6 commits into from
Mar 17, 2020

Conversation

hidmic
Copy link
Contributor

@hidmic hidmic commented Mar 10, 2020

Addresses #120. This pull request drops the concept of a default launch description in lieu of a so called ROS adapter accessible from and managed by a launch context. Actions that make use of it can trigger implicit and idempotent instantiation of such adapter, though a user may do so explicitly.

Copy link
Member

@jacobperron jacobperron left a comment

Choose a reason for hiding this comment

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

Thanks for working on this :)

Feedback below.

ros2launch/ros2launch/api/api.py Outdated Show resolved Hide resolved
launch_ros/launch_ros/ros_adapters.py Outdated Show resolved Hide resolved
launch_ros/launch_ros/ros_adapters.py Outdated Show resolved Hide resolved
launch_ros/launch_ros/ros_adapters.py Outdated Show resolved Hide resolved
launch_ros/launch_ros/ros_adapters.py Outdated Show resolved Hide resolved
import rclpy


class ExplicitROSStartup(launch.actions.OpaqueFunction):
Copy link
Member

Choose a reason for hiding this comment

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

I'm guessing that the purpose of this action is to allow users to initialize the ROS adapter. But, I don't think we should encourage users to use the launch ROS context (or node) directly. It is more of an implementation detail of launch_ros. It could also lead to misuse (e.g. I can imagine a user passing the node to their own executor).

I think it makes more sense for users to create their own nodes and manage their own contexts if they need them, and it doesn't involve too much overhead.

Maybe someone else can weigh in here.

Copy link
Member

Choose a reason for hiding this comment

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

👍, keep the launch_ros context and node hidden, let the user create their own if they want to use ROS for something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can see how this could lead to misuse, yeah. But on the other hand, we'd be disallowing any form of ROS context configuration, which was previously possible via ROSSpecificLaunchStartup. So I'd be personally inclined towards simply documenting this properly.

One last thought. It may prove useful to allow users to create nodes and have launch servicing them and handling their lifetime.

Copy link
Member

Choose a reason for hiding this comment

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

One last thought. It may prove useful to allow users to create nodes and have launch servicing them and handling their lifetime.

We could provide that, but I think that's a different use case than this class. This class is useful (imo) for other actions to build on. If the we wanted to share something with the user and manage the lifetime for them, then we need a way to know when they are done with it and a way to give them access to the node and context we created. Though there is a lot of overlap, it seems like a different use case to me.

Copy link
Member

Choose a reason for hiding this comment

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

Having the ability to configure the context seems compelling to me, but I wonder if there are other ways to allow that without exposing this to the user.

Copy link
Contributor Author

@hidmic hidmic Mar 11, 2020

Choose a reason for hiding this comment

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

Following @wjwwood rationale, if users won't be making use of this class for their own purposes then there's no need for the context to be configurable. See f5fd17b.

Copy link
Member

@ivanpauno ivanpauno left a comment

Choose a reason for hiding this comment

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

@hidmic why is ExplicitROSStartup needed?
IMO, I would just provide implicit startup, though I don't mind strongly.

I've left some other comments.

import rclpy


class ExplicitROSStartup(launch.actions.OpaqueFunction):
Copy link
Member

Choose a reason for hiding this comment

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

why inheriting from OpaqueFunction? why just not inheriting from Action and overriding the execute method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mimic'd ROSSpecificLaunchStartup structure. Don't mind either way.

Copy link
Contributor Author

@hidmic hidmic Mar 11, 2020

Choose a reason for hiding this comment

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

Action dropped in f5fd17b.

# Create a subscription to monitor the state changes of the subprocess.
self.__rclpy_subscription = context.locals.launch_ros_node.create_subscription(
self.__rclpy_subscription = node.create_subscription(
Copy link
Member

Choose a reason for hiding this comment

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

What if ExplicitROSStartup wasn't executed before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then get_ros_node will ensure node comes into existence.

context.register_event_handler(launch.event_handlers.OnShutdown(
on_shutdown=lambda *args, **kwargs: ros_adapter.shutdown()
))
return context.locals['ros_adapter']
Copy link
Member

Choose a reason for hiding this comment

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

Should we consider making this thread-safe? I'm just imaging some action creating a thread and trying to access this from the thread and another action accessing it from the asyncio run loop in a different thread, at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I had the impression launch wasn't thread-safe i.e. that using the same launch context across asyncio loops in separate threads wasn't supported. We can lock it otherwise.

Copy link
Member

Choose a reason for hiding this comment

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

Parts of it are, if we think a function may be called outside of the asyncio loop (like from a user thread) then it should be thread-safe. I'm not talking about calling the run loop for asyncio in multiple threads concurrently, that is definitely not supported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. The thing is that something like:

I'm just imaging some action creating a thread and trying to access this from the thread and another action accessing it from the asyncio run loop in a different thread, at the same time.

won't work properly because launch.Context itself isn't thread-safe e.g. one cannot emit an event or affect context globals or locals in a thread-safe manner. So I'd rather push for that in a follow-up PR i.e. make launch.Context thread-safe and then make this function thread-safe. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

The context is not thread-safe, but things that use it are, e.g.: https://github.com/ros2/launch/blob/0d040023306843d71b6316b160fab7b219f41892/launch/launch/launch_service.py#L124-L144

I'd say I agree with you if the user or other actions were accessing the context directly, but they're not, they are calling this function which happens to use the context as an implementation detail.

I think it should still be thread-safe to be honest.

Copy link
Contributor Author

@hidmic hidmic Mar 12, 2020

Choose a reason for hiding this comment

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

Why not just a lock in this function? Either a static (global) one or a one owned by this class?

Because of the reasons exposed above. Also, won't that result in a false sense of safety? There's very little inside launch that is actually thread-safe. And action authors can still use multiple threads by grabbing the node in their execute() method, so we're not blocking a use case.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry I'll try to address the two points directly.

(a) two threads using two different launch.LaunchContext instances may contend with each other for no reason

Is that actually a problem? The scope and time bounds on this function are straightforward, so I don't think there's a deadlock problem. Are you concerned about performance due to lock contention?

(b) launch.LaunchContext global dict itself isn't thread-safe (which may be solved with such a lock, yes, but if and only if no other API that could potentially run concurrently attempts to do the same).

Of course if someone trys to access this fields in the context directly it may be an issue, but I thought the idea was to always access this field of the context (context.locals.ros_adapter) via this method right?

Do you mean issues when someone asynchronously calls this function while someone else concurrently accesses a different field in the context?


I'm happy to drop this if you don't think it's a concern, my concern is definitely academic for the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you concerned about performance due to lock contention?

A bit of that, a bit of OCD :)

Do you mean issues when someone asynchronously calls this function while someone else concurrently accesses a different field in the context?

Precisely. In particular, launch.LaunchContext.extend_locals() and launch.LaunchContext.extend_globals() do not seem safe to use concurrently as per this Python 3.8 FAQ (see disclaimer at the bottom, due to __del__ being called as a side effect of dict.update). I give you that the chance of actually hitting a problem is (likely very) low though.


If you think there's no risk of this backfiring in the future, I'll add that lock.

Copy link
Member

Choose a reason for hiding this comment

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

We can leave it out. It would be nice to mention it explicitly in the docstring, as I feel this class is more likely to be used concurrently (like in the callback of a subscription or something) than other context functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 63ff30f.

launch_ros/launch_ros/ros_adapters.py Outdated Show resolved Hide resolved
@hidmic hidmic force-pushed the hidmic/implicit-startup branch from 577d77b to f5fd17b Compare March 11, 2020 17:24
Copy link
Member

@jacobperron jacobperron left a comment

Choose a reason for hiding this comment

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

LGTM

@hidmic hidmic requested a review from ivanpauno March 12, 2020 14:21
@hidmic
Copy link
Contributor Author

hidmic commented Mar 12, 2020

CI up to test_launch_ros and test_communication to put this to test:

  • Linux Build Status (unrelated test failures for WString publishers using CycloneDDS)
  • Linux-aarch64 Build Status
  • MacOS Build Status (unrelated test failures for WString publishers using CycloneDDS)
  • Windows Build Status (unrelated test failures for WString publishers using CycloneDDS)

@@ -19,19 +19,4 @@


class LaunchTestRunner(launch_testing.test_runner.LaunchTestRunner):
Copy link
Member

Choose a reason for hiding this comment

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

We should consider deprecating launch_testing_ros

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about it, but I chose not to do that now both to keep this PR small and to not take that leap and remove a package we may need again afterwards.

hidmic added 5 commits March 12, 2020 15:49
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic force-pushed the hidmic/implicit-startup branch from 9c6f840 to 5476e3d Compare March 12, 2020 18:51
@hidmic
Copy link
Contributor Author

hidmic commented Mar 12, 2020

Had to rebase to solve conflicts, sorry folks!

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic requested a review from wjwwood March 16, 2020 13:34
@hidmic
Copy link
Contributor Author

hidmic commented Mar 17, 2020

Alright, going in! Thank you all for the reviews!

@hidmic hidmic merged commit 8cc6df2 into master Mar 17, 2020
@delete-merged-branch delete-merged-branch bot deleted the hidmic/implicit-startup branch March 17, 2020 13:40
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.

5 participants