-
Notifications
You must be signed in to change notification settings - Fork 79
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
Conversation
There was a problem hiding this 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.
import rclpy | ||
|
||
|
||
class ExplicitROSStartup(launch.actions.OpaqueFunction): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 63ff30f.
577d77b
to
f5fd17b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
CI up to |
@@ -19,19 +19,4 @@ | |||
|
|||
|
|||
class LaunchTestRunner(launch_testing.test_runner.LaunchTestRunner): |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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>
9c6f840
to
5476e3d
Compare
Had to rebase to solve conflicts, sorry folks! |
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Alright, going in! Thank you all for the reviews! |
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.