From 5d36f774a7a1226f0e73007b27f2c2cf9607210c Mon Sep 17 00:00:00 2001 From: Mehsias <40789989+Mehsias@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:57:57 +0100 Subject: [PATCH 1/6] fix(params): prevent parameter retrieval crashes in ROS2 - Add check for node's fully qualified name to prevent deadlocks - Add 2.0s timeout to spin_until_future_complete - Fix crashes when retrieving parameters via Roslibjs Fixes #972 --- rosapi/src/rosapi/params.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rosapi/src/rosapi/params.py b/rosapi/src/rosapi/params.py index 36af6913f..aa27a4e45 100644 --- a/rosapi/src/rosapi/params.py +++ b/rosapi/src/rosapi/params.py @@ -226,7 +226,7 @@ def _get_param_names(node_name): # This method is called in a service callback; calling a service of the same node # will cause a deadlock. global _parent_node_name - if node_name == _parent_node_name: + if node_name == _parent_node_name or node_name == _node.get_fully_qualified_name(): return [] client = _node.create_client(ListParameters, f"{node_name}/list_parameters") @@ -238,7 +238,7 @@ def _get_param_names(node_name): request = ListParameters.Request() future = client.call_async(request) if _node.executor: - _node.executor.spin_until_future_complete(future) + _node.executor.spin_until_future_complete(future, timeout_sec=2.0) else: rclpy.spin_until_future_complete(_node, future) response = future.result() From d9c13579e3a74feb0b4da65bf4b551e41482a0f5 Mon Sep 17 00:00:00 2001 From: "Matthias Rathauscher (LWN)" Date: Mon, 13 Jan 2025 15:30:01 +0100 Subject: [PATCH 2/6] added parameter for timeout --- rosapi/scripts/rosapi_node | 6 +++++- rosapi/src/rosapi/params.py | 18 +++++++++++++----- .../launch/rosbridge_websocket_launch.xml | 2 ++ rosbridge_server/scripts/rosbridge_websocket | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/rosapi/scripts/rosapi_node b/rosapi/scripts/rosapi_node index af478ee52..f2e014040 100755 --- a/rosapi/scripts/rosapi_node +++ b/rosapi/scripts/rosapi_node @@ -77,6 +77,7 @@ class Rosapi(Node): self.declare_parameter("topics_glob", "[*]") self.declare_parameter("services_glob", "[*]") self.declare_parameter("params_glob", "[*]") + self.declare_parameter("params_timeout", 5.0) self.globs = self.get_globs() self.register_services() @@ -87,7 +88,10 @@ class Rosapi(Node): full_name = self.get_namespace() + self.get_name() else: full_name = self.get_namespace() + "/" + self.get_name() - params.init(full_name) + + timeout_sec = self.get_parameter("params_timeout").value + params.init(full_name, timeout_sec) + self.create_service(Topics, "/rosapi/topics", self.get_topics) self.create_service(TopicsForType, "/rosapi/topics_for_type", self.get_topics_for_type) self.create_service( diff --git a/rosapi/src/rosapi/params.py b/rosapi/src/rosapi/params.py index aa27a4e45..bce06f25e 100644 --- a/rosapi/src/rosapi/params.py +++ b/rosapi/src/rosapi/params.py @@ -45,10 +45,14 @@ """ Methods to interact with the param server. Values have to be passed as JSON in order to facilitate dynamically typed SRV messages """ +# Constants +DEFAULT_PARAM_TIMEOUT_SEC = 5.0 + # Ensure thread safety for setting / getting parameters. param_server_lock = threading.RLock() _node = None _parent_node_name = "" +_timeout_sec = DEFAULT_PARAM_TIMEOUT_SEC _parameter_type_mapping = [ "", @@ -64,12 +68,12 @@ ] -def init(parent_node_name): +def init(parent_node_name, timeout_sec=DEFAULT_PARAM_TIMEOUT_SEC): """ Initializes params module with a rclpy.node.Node for further use. This function has to be called before any other for the module to work. """ - global _node, _parent_node_name + global _node, _parent_node_name, _timeout_sec # TODO(@jubeira): remove this node; use rosapi node with MultiThreadedExecutor or # async / await to prevent the service calls from blocking. parent_node_basename = parent_node_name.split("/")[-1] @@ -81,6 +85,10 @@ def init(parent_node_name): ) _parent_node_name = get_absolute_node_name(parent_node_name) + if not isinstance(timeout_sec, (int, float)) or timeout_sec <= 0: + raise ValueError("Parameter timeout must be a positive number") + _timeout_sec = timeout_sec + def set_param(node_name, name, value, params_glob): """Sets a parameter in a given node""" @@ -231,16 +239,16 @@ def _get_param_names(node_name): client = _node.create_client(ListParameters, f"{node_name}/list_parameters") - ready = client.wait_for_service(timeout_sec=5.0) + ready = client.wait_for_service(timeout_sec=_timeout_sec) if not ready: raise RuntimeError("Wait for list_parameters service timed out") request = ListParameters.Request() future = client.call_async(request) if _node.executor: - _node.executor.spin_until_future_complete(future, timeout_sec=2.0) + _node.executor.spin_until_future_complete(future, timeout_sec=_timeout_sec) else: - rclpy.spin_until_future_complete(_node, future) + rclpy.spin_until_future_complete(_node, future, timeout_sec=_timeout_sec) response = future.result() if response is not None: diff --git a/rosbridge_server/launch/rosbridge_websocket_launch.xml b/rosbridge_server/launch/rosbridge_websocket_launch.xml index 389b2ff15..f0ce6b3fb 100644 --- a/rosbridge_server/launch/rosbridge_websocket_launch.xml +++ b/rosbridge_server/launch/rosbridge_websocket_launch.xml @@ -22,6 +22,7 @@ + @@ -76,5 +77,6 @@ + diff --git a/rosbridge_server/scripts/rosbridge_websocket b/rosbridge_server/scripts/rosbridge_websocket index 647069442..c5eccad0b 120000 --- a/rosbridge_server/scripts/rosbridge_websocket +++ b/rosbridge_server/scripts/rosbridge_websocket @@ -1 +1 @@ -rosbridge_websocket.py \ No newline at end of file +rosbridge_websocket.py From fbed9318e905038907b91bbc5c8cbbb0b60550ce Mon Sep 17 00:00:00 2001 From: Lebecque Florian Date: Sat, 4 Jan 2025 20:06:39 +0100 Subject: [PATCH 3/6] Add new service to retrieve the different interfaces in the ROS Network (#988) --- rosapi/scripts/rosapi_node | 7 +++++++ rosapi/src/rosapi/proxy.py | 6 ++++++ rosapi_msgs/CMakeLists.txt | 1 + rosapi_msgs/srv/Interfaces.srv | 2 ++ 4 files changed, 16 insertions(+) create mode 100644 rosapi_msgs/srv/Interfaces.srv diff --git a/rosapi/scripts/rosapi_node b/rosapi/scripts/rosapi_node index f2e014040..be7d256b4 100755 --- a/rosapi/scripts/rosapi_node +++ b/rosapi/scripts/rosapi_node @@ -48,6 +48,7 @@ from rosapi_msgs.srv import ( GetROSVersion, GetTime, HasParam, + Interfaces, MessageDetails, NodeDetails, Nodes, @@ -93,6 +94,7 @@ class Rosapi(Node): params.init(full_name, timeout_sec) self.create_service(Topics, "/rosapi/topics", self.get_topics) + self.create_service(Interfaces, "/rosapi/interfaces", self.get_interfaces) self.create_service(TopicsForType, "/rosapi/topics_for_type", self.get_topics_for_type) self.create_service( TopicsAndRawTypes, @@ -141,6 +143,11 @@ class Rosapi(Node): response.topics, response.types = proxy.get_topics_and_types(self.globs.topics) return response + def get_interfaces(self, request, response): + """Called by the rosapi/Types service. Returns a list of all the types in the system.""" + response.interfaces = proxy.get_interfaces() + return response + def get_topics_for_type(self, request, response): """Called by the rosapi/TopicsForType service. Returns a list of all the topics that are publishing a given type""" response.topics = proxy.get_topics_for_type(request.type, self.globs.topics) diff --git a/rosapi/src/rosapi/proxy.py b/rosapi/src/rosapi/proxy.py index c3bdbb4a0..48ed7835f 100644 --- a/rosapi/src/rosapi/proxy.py +++ b/rosapi/src/rosapi/proxy.py @@ -31,6 +31,7 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from ros2interface.api import type_completer from ros2node.api import ( get_node_names, get_publisher_info, @@ -60,6 +61,11 @@ def get_topics(topics_glob, include_hidden=False): return filter_globs(topics_glob, topic_names) +def get_interfaces(): + """Returns a list of all the types in the ROS system""" + return type_completer() + + def get_topics_and_types(topics_glob, include_hidden=False): return get_publications_and_types( topics_glob, get_topic_names_and_types, include_hidden_topics=include_hidden diff --git a/rosapi_msgs/CMakeLists.txt b/rosapi_msgs/CMakeLists.txt index 477ab48e4..8764e26c8 100644 --- a/rosapi_msgs/CMakeLists.txt +++ b/rosapi_msgs/CMakeLists.txt @@ -14,6 +14,7 @@ rosidl_generate_interfaces(${PROJECT_NAME} srv/GetROSVersion.srv srv/GetTime.srv srv/HasParam.srv + srv/Interfaces.srv srv/MessageDetails.srv srv/Nodes.srv srv/NodeDetails.srv diff --git a/rosapi_msgs/srv/Interfaces.srv b/rosapi_msgs/srv/Interfaces.srv new file mode 100644 index 000000000..53355592a --- /dev/null +++ b/rosapi_msgs/srv/Interfaces.srv @@ -0,0 +1,2 @@ +--- +string[] interfaces From edf8df84da046e4a54652b222ccfa5439d9c1985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Sowa?= Date: Sat, 18 Jan 2025 13:03:02 +0000 Subject: [PATCH 4/6] Fix rosbridge_websocket symlink --- rosbridge_server/scripts/rosbridge_websocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosbridge_server/scripts/rosbridge_websocket b/rosbridge_server/scripts/rosbridge_websocket index c5eccad0b..647069442 120000 --- a/rosbridge_server/scripts/rosbridge_websocket +++ b/rosbridge_server/scripts/rosbridge_websocket @@ -1 +1 @@ -rosbridge_websocket.py +rosbridge_websocket.py \ No newline at end of file From 90fefb90474304bc982df3ae5b4b9b7f18ff4511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Sowa?= Date: Sat, 25 Jan 2025 21:54:18 +0100 Subject: [PATCH 5/6] Don't fail on nodes without parameter services --- rosapi/src/rosapi/params.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rosapi/src/rosapi/params.py b/rosapi/src/rosapi/params.py index bce06f25e..51513f14f 100644 --- a/rosapi/src/rosapi/params.py +++ b/rosapi/src/rosapi/params.py @@ -239,9 +239,8 @@ def _get_param_names(node_name): client = _node.create_client(ListParameters, f"{node_name}/list_parameters") - ready = client.wait_for_service(timeout_sec=_timeout_sec) - if not ready: - raise RuntimeError("Wait for list_parameters service timed out") + if not client.service_is_ready(): + return [] request = ListParameters.Request() future = client.call_async(request) From fd229c471f7b8124afa72d2cde1f2aafa88845bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Sowa?= Date: Sat, 25 Jan 2025 21:59:57 +0100 Subject: [PATCH 6/6] Remove added empty line --- rosapi/scripts/rosapi_node | 1 - 1 file changed, 1 deletion(-) diff --git a/rosapi/scripts/rosapi_node b/rosapi/scripts/rosapi_node index 7a63c2fed..3ef910b71 100755 --- a/rosapi/scripts/rosapi_node +++ b/rosapi/scripts/rosapi_node @@ -96,7 +96,6 @@ class Rosapi(Node): self.create_service(Topics, "~/topics", self.get_topics) self.create_service(Interfaces, "~/interfaces", self.get_interfaces) self.create_service(TopicsForType, "~/topics_for_type", self.get_topics_for_type) - self.create_service( TopicsAndRawTypes, "~/topics_and_raw_types",