-
Notifications
You must be signed in to change notification settings - Fork 224
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
Synchronous service client call broken in timer callback #1016
Comments
Have you found any workarounds? I think this is affecting my codebase as well. It's pretty heavy in its use of the MultiThreadedExecutor and synchronous callbacks within services/timers. |
This commit affects.
about "handler_wrapper()"
|
I just found this as well. This breaks all usages of the MultiThreadedExecutor at the moment that rely on actual multithreading. I presume |
@apockill @usako-lab really appreciate for the issue and contribution, that really helps. I will check the code and address the issue tomorrow. |
We also ran into this issue and can confirm that the temporary solution proposed by @usako-lab fixes the problem. Unfortunately there seems to be convenient way to revert back to an older version via apt or rosdep, meaning the only option is to build from source. It is rather inconvenient that this is the case, as it means any breaking change that is pushed officially will break our system when installing from the official sources (which always do the latest available version). If anyone has experienced the same, how did you deal with this problem? |
@ramlab-jose it's a bit hacky, but I subclassed MultithreadedExecutor and monkeypatched the function with a corrected function. This fit my workflow well because in our codebase we were already subclassing and adding some extra functionality. |
diff --git a/rclpy/rclpy/executors.py b/rclpy/rclpy/executors.py
index 5c131e2..665338c 100644
--- a/rclpy/rclpy/executors.py
+++ b/rclpy/rclpy/executors.py
@@ -14,6 +14,7 @@
from concurrent.futures import ThreadPoolExecutor
from contextlib import ExitStack
+from functools import partial
import inspect
import multiprocessing
from threading import Condition
@@ -769,7 +770,7 @@ class MultiThreadedExecutor(Executor):
handler()
if handler.exception() is not None:
raise handler.exception()
- self._executor.submit(handler_wrapper(handler))
+ self._executor.submit(partial(handler_wrapper, handler))
def spin_once(self, timeout_sec: float = None) -> None:
self._spin_once_impl(timeout_sec) |
@ramlab-jose Just to note, you don't actually have to build from source to fix this issue with the above patch. Using the binary install I edited the file |
ros2/rclpy#1016 Signed-off-by: Tomoya Fujita <[email protected]>
this sounds reasonable to me, user expects that could work with |
So, while this should be fixed, I want to point out that relying on the MultiThreadedExecutor in this way is fragile. I think a better way which also works with single threaded executors would look like this (tested it on rolling): diff --git a/test_client_orig.py b/test_client.py
index 323d585..c25bd96 100644
--- a/test_client_orig.py
+++ b/test_client.py
@@ -2,7 +2,6 @@ from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
-from rclpy.executors import MultiThreadedExecutor
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup
@@ -23,7 +22,7 @@ class MinimalClient(Node):
1.0, self.main_loop, callback_group=self.timer_cb
)
- def send_request(self, a, b):
+ async def send_request(self, a, b):
self.get_logger().info("send_request: enter")
self.req.a = a
self.req.b = b
@@ -34,11 +33,11 @@ class MinimalClient(Node):
# self.get_logger().info("send_request: exit")
# return self.future.result()
- # Used to work, but blocks now
- return self.cli.call(self.req)
+ # Better way? using asyncio
+ return await self.cli.call_async(self.req)
- def main_loop(self) -> None:
- response = self.send_request(4, 2)
+ async def main_loop(self) -> None:
+ response = await self.send_request(4, 2)
self.get_logger().info(
"Result of add_two_ints: for %d + %d = %d" % (4, 2, response.sum)
)
@@ -49,9 +48,7 @@ def main(args=None):
minimal_client = MinimalClient()
- executor = MultiThreadedExecutor()
- executor.add_node(minimal_client)
- executor.spin()
+ rclpy.spin(minimal_client)
rclpy.shutdown()
That works fine, but it is still important in this situation that the timer and service client are in separate callback groups. Because while control is returned to the executor on the |
it works without "partial", belated. |
thanks, i have tried it already, it fixes this problem but regenerates #983 |
I can confirm replicating error on rolling mainline.
|
folks, really appreciate the all of the information and iterating with us. #1017 has been reverted including all distro foxy/galactic/humble, i will close this issue. |
Other than just reverting in #1017, I would really appreciate adding a regression test to make sure this issue never happens again. For example, making sure nested callbacks with different callback groups work. We lost several days because of this issue. Production code depends on this feature. |
Yes, I agree. We wanted to get this fixed and out quickly, so we didn't stop to do that, but a test would be ideal. Would you like to submit a pull request that adds one? |
Is there any apt release with the fix ? If not, when it will be created, please ? The newest release, which i can found, is from 19. September. |
@JakubNejedly apt install ros-foxy-rclpy>=1.0.11-* |
Bug report
Required Info:
Steps to reproduce issue
Use the minimal service server from the examples
and a slightly modified service client:
Run the service server in one terminal and the client in another.
Expected behavior
You repeatedly see the client call the server every second and get a response.
Actual behavior
The client hangs at the first call
Additional information
Previous to the 2022-09-28 patch release of foxy, the above minimal client was functional. However, the
call_async(...)
version that is currently commented out didn't work before in that it would run once and then never fire the timer again. This is despite the fact that inrclcpp
this is exactly one of the recommended methods to implement this kind of behavior, and, from the documentation, would seem like the recommended way to do it inrclpy
as well. It didn't work, so we found an alternative method: calling the synchronouscall(...)
with a multi-threaded executor. As stated, this then broke with the push of the 2022-09-28 release. This seriously hampers our ability to use ROS2 as there is essentially no way to perform the above logic, and is a breaking change.The text was updated successfully, but these errors were encountered: