Fix recursive spinning from within a callback? #1211
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Resolves #1018. Also #919 seems related at first glance.
Suggested changes:
rclpy.spin*()
) use the node's current executor by default, if set. Only in case the passednode
does not have an executor assigned yet, fall back to the default executor, for example in the main function.add_node()
may returnFalse
if the node was already added to the executor before, like ifspin_until_future_complete()
was called recursively from within another callback. In that case it must not be removed in the same callback. Only the outermost call would do so.Rationale:
This is to support the common case where
rclpy.spin_until_future_complete(self, future)
is used within another callback, by a running executor, where the node has already been added before. At the moment, without a patch like this, it seems very complex to call a service in a blocking way in this case, for example to forward a call to another service within a node? The synchronousclient.call()
cannot be used either, which would be the most obvious solution, because it does not spin the node while waiting or handles the response directly, and hence depends on a spinner for that node in another thread to function properly.The basic example for calling a service from the official documentation only works because the service call is initiated from the main thread directly, which is unusual in a more complex, long running node. It would also work like that inside a callback, for example a timer callback like in #1018, but then the node is removed from the executor and no other callbacks will run anymore afterwards.
I am aware that this patch would be a quite significant change to
rclpy
, with the potential to break user code. So it is more meant to start a discussion on other potential solutions...Alternatives considered:
node.executor.spin_until_future_complete(future)
instead ofrclpy.spin_until_future_complete(node, future)
if that is used within a callback?rclcpp
, to enforce the above?References:
For reference, my original comment in #1018 with some additional thoughts and suggestions:
And just for completeness and because it is very much related, if an uncaught exception is thrown in any callback in
rclcpp
, the executor is left in an unclean state and the node cannot spin anymore, even if exceptions would be handled by the caller of the spinner: ros2/rclcpp#2017.