From 803b9e79204e91b4cf34d33353e91bcf05dd4407 Mon Sep 17 00:00:00 2001 From: Spir0u <28966599+Spir0u@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:08:13 +0100 Subject: [PATCH 1/4] add server timeout exception --- rosbridge_library/src/rosbridge_library/internal/actions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rosbridge_library/src/rosbridge_library/internal/actions.py b/rosbridge_library/src/rosbridge_library/internal/actions.py index ce3a133aa..91c9a217b 100644 --- a/rosbridge_library/src/rosbridge_library/internal/actions.py +++ b/rosbridge_library/src/rosbridge_library/internal/actions.py @@ -195,7 +195,8 @@ def send_goal( client: ActionClient[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] = ActionClient( node_handle, action_class, action_name ) - client.wait_for_server(timeout_sec=self.server_timeout_time) + if( not client.wait_for_server(timeout_sec=self.server_timeout_time)): + raise Exception("No action server available") send_goal_future = client.send_goal_async(inst, feedback_callback=feedback_cb) # type: ignore[arg-type] send_goal_future.add_done_callback(self.goal_response_cb) From 1476281b6926bc7a8ead868fa4cc881a8218702d Mon Sep 17 00:00:00 2001 From: Spir0u <28966599+Spir0u@users.noreply.github.com> Date: Tue, 25 Nov 2025 00:08:23 +0100 Subject: [PATCH 2/4] forward rejected goal handles The goal_response_cb should not throw an exception, so the exception is passed in the result and then raised in send_goal --- .../src/rosbridge_library/internal/actions.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rosbridge_library/src/rosbridge_library/internal/actions.py b/rosbridge_library/src/rosbridge_library/internal/actions.py index 91c9a217b..b5378922d 100644 --- a/rosbridge_library/src/rosbridge_library/internal/actions.py +++ b/rosbridge_library/src/rosbridge_library/internal/actions.py @@ -146,7 +146,7 @@ def args_to_action_goal_instance(inst: ROSMessage, args: list | dict[str, Any] | class SendGoal(Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]): """Helper class to send action goals.""" - result: GetResultServiceResponse[ROSActionResultT] | None = None + result: GetResultServiceResponse[ROSActionResultT] | Exception | None = None def __init__(self, server_timeout_time: float = 1.0, sleep_time: float = 0.001) -> None: self.server_timeout_time = server_timeout_time @@ -166,7 +166,8 @@ def goal_response_cb( assert self.goal_handle is not None if not self.goal_handle.accepted: msg = "Action goal was rejected" - raise Exception(msg) + self.result = Exception(msg) + return result_future: Future[GetResultServiceResponse[ROSActionResultT]] = ( self.goal_handle.get_result_async() ) @@ -204,6 +205,10 @@ def send_goal( time.sleep(self.sleep_time) client.destroy() + + if isinstance(self.result, Exception): + raise self.result + if self.result is not None: # Turn the response into JSON and pass to the callback json_response = extract_values(self.result) From 7a1d9941840c8e4983d7a3633650fec10ffa7659 Mon Sep 17 00:00:00 2001 From: Spir0u <28966599+Spir0u@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:59:16 +0100 Subject: [PATCH 3/4] return an empty result when result was None due to cancellation/abortion --- .../rosbridge_library/capabilities/advertise_action.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rosbridge_library/src/rosbridge_library/capabilities/advertise_action.py b/rosbridge_library/src/rosbridge_library/capabilities/advertise_action.py index ef119bb42..0fc963aeb 100644 --- a/rosbridge_library/src/rosbridge_library/capabilities/advertise_action.py +++ b/rosbridge_library/src/rosbridge_library/capabilities/advertise_action.py @@ -103,10 +103,6 @@ def done_callback(fut: Future[ROSActionResultT]) -> None: if fut.cancelled(): goal.abort() self.protocol.log("info", f"Aborted goal {goal_id}") - # Send an empty result to avoid stack traces - fut.set_result( - cast("ROSActionResultT", get_action_class(self.action_type).Result()) - ) else: if goal_id not in self.goal_statuses: goal.abort() @@ -138,7 +134,9 @@ def done_callback(fut: Future[ROSActionResultT]) -> None: try: result = await future - assert result is not None, "Action result cannot be None" + if result is None: + # Return empty result when cancelled/aborted + return cast("ROSActionResultT", get_action_class(self.action_type).Result()) return result finally: del self.goal_futures[goal_id] From 50679668c8c45f25dabeec4851233124a10cce8b Mon Sep 17 00:00:00 2001 From: Spir0u <28966599+Spir0u@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:34:03 +0100 Subject: [PATCH 4/4] fix lint error --- rosbridge_library/src/rosbridge_library/internal/actions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rosbridge_library/src/rosbridge_library/internal/actions.py b/rosbridge_library/src/rosbridge_library/internal/actions.py index b5378922d..d45e7bd8d 100644 --- a/rosbridge_library/src/rosbridge_library/internal/actions.py +++ b/rosbridge_library/src/rosbridge_library/internal/actions.py @@ -196,8 +196,9 @@ def send_goal( client: ActionClient[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] = ActionClient( node_handle, action_class, action_name ) - if( not client.wait_for_server(timeout_sec=self.server_timeout_time)): - raise Exception("No action server available") + if not client.wait_for_server(timeout_sec=self.server_timeout_time): + msg = "No action server available" + raise Exception(msg) send_goal_future = client.send_goal_async(inst, feedback_callback=feedback_cb) # type: ignore[arg-type] send_goal_future.add_done_callback(self.goal_response_cb)