Skip to content

Commit ef39b60

Browse files
authored
add timeout arguments elsewhere (#1185)
* add timeout for "ros2 service call". Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * add timeout for "ros2 action send_goal". Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * add timeout for "ros2 component". Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * add timeout for "ros2 lifecycle". Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * add timeout for "ros2param". Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> --------- Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
1 parent 4018764 commit ef39b60

19 files changed

Lines changed: 195 additions & 74 deletions

File tree

ros2action/ros2action/verb/send_goal.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ def add_arguments(self, parser, cli_name):
6868
parser.add_argument(
6969
'-t', '--timeout', metavar='N', type=non_negative_int, default=None,
7070
help=(
71-
'Wait for N seconds until server becomes available and goal is completed '
72-
'(default waits indefinitely)'
71+
'Wait for N seconds until action server becomes available, '
72+
'goal is sent, and result is received. '
73+
'Also applies to goal cancellation if interrupted. '
74+
'(default: waits indefinitely)'
7375
))
7476

7577
def main(self, *, args):
@@ -145,7 +147,11 @@ def send_goal(action_name, action_type, goal_values, feedback_callback, timeout=
145147

146148
print('Sending goal:\n {}'.format(message_to_yaml(goal)))
147149
goal_future = action_client.send_goal_async(goal, feedback_callback)
148-
rclpy.spin_until_future_complete(node, goal_future)
150+
rclpy.spin_until_future_complete(node, goal_future, timeout_sec=timeout)
151+
152+
if not goal_future.done():
153+
print(f'Timed out waiting for goal acceptance after {timeout} seconds.')
154+
return
149155

150156
goal_handle = goal_future.result()
151157

@@ -158,7 +164,11 @@ def _sigint_cancel_handler(sig, frame):
158164
GoalStatus.STATUS_EXECUTING == goal_handle.status)):
159165
print('Canceling goal...')
160166
cancel_future = goal_handle.cancel_goal_async()
161-
rclpy.spin_until_future_complete(node, cancel_future)
167+
rclpy.spin_until_future_complete(node, cancel_future, timeout_sec=timeout)
168+
169+
if not cancel_future.done():
170+
raise RuntimeError(
171+
f'Timed out waiting for goal cancellation after {timeout} seconds.')
162172

163173
cancel_response = cancel_future.result()
164174

ros2component/ros2component/api/__init__.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,29 +68,31 @@ def get_registered_component_types():
6868
ComponentInfo = namedtuple('Component', ('uid', 'name'))
6969

7070

71-
def get_components_in_container(*, node, remote_container_node_name):
71+
def get_components_in_container(*, node, remote_container_node_name, timeout=5.0):
7272
"""
7373
Get information about the components in a container.
7474
7575
:param node: an `rclpy.Node` instance.
7676
:param remote_container_node_names: of the container node to inspect.
77+
:param timeout: maximum time to wait for response in seconds (default: 5.0).
7778
:return: a tuple with either a truthy boolean and a list of `ComponentInfo`
7879
instances containing the unique id and name of each component or a falsy
7980
boolean and a reason string in case of error.
8081
"""
8182
return get_components_in_containers(
82-
node=node, remote_containers_node_names=[remote_container_node_name]
83+
node=node, remote_containers_node_names=[remote_container_node_name], timeout=timeout
8384
)[remote_container_node_name]
8485

8586

86-
def get_components_in_containers(*, node, remote_containers_node_names):
87+
def get_components_in_containers(*, node, remote_containers_node_names, timeout=5.0):
8788
"""
8889
Get information about the components in multiple containers.
8990
9091
Get information about the components in a container.
9192
9293
:param node: an `rclpy.Node` instance.
9394
:param remote_container_node_names: of the container nodes to inspect.
95+
:param timeout: maximum time to wait for response in seconds (default: 5.0).
9496
:return: a dict of tuples, with either a truthy boolean and a list of `ComponentInfo`
9597
instances containing the unique id and name of each component or a falsy boolean and
9698
a reason string in case of error, per container node.
@@ -174,7 +176,7 @@ def _resume(to_completion=False):
174176

175177
timer = node.create_timer(timer_period_sec=0.1, callback=resume)
176178
try:
177-
rclpy.spin_until_future_complete(node, future, timeout_sec=5.0)
179+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
178180
if not future.done():
179181
resume(to_completion=True)
180182
return dict(future.result())
@@ -193,7 +195,8 @@ def load_component_into_container(
193195
log_level=None,
194196
remap_rules=None,
195197
parameters=None,
196-
extra_arguments=None
198+
extra_arguments=None,
199+
timeout=None
197200
):
198201
"""
199202
Load component into a running container synchronously.
@@ -208,6 +211,7 @@ def load_component_into_container(
208211
:param remap_rules: remapping rules for the component node, in the 'from:=to' form
209212
:param parameters: optional parameters for the component node, in the 'name:=value' form
210213
:param extra_arguments: arguments specific to the container node in the 'name:=value' form
214+
:param timeout: maximum time to wait for response in seconds (default: waits indefinitely)
211215
"""
212216
load_node_client = node.create_client(
213217
composition_interfaces.srv.LoadNode,
@@ -244,7 +248,11 @@ def load_component_into_container(
244248
arg_msg.name = name
245249
request.extra_arguments.append(arg_msg)
246250
future = load_node_client.call_async(request)
247-
rclpy.spin_until_future_complete(node, future)
251+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
252+
if not future.done():
253+
raise RuntimeError(
254+
'Timed out waiting for load_node response from '
255+
f'{remote_container_node_name!r} container (timeout: {timeout}s)')
248256
response = future.result()
249257
if not response.success:
250258
raise RuntimeError('Failed to load component: ' + response.error_message.capitalize())
@@ -253,13 +261,15 @@ def load_component_into_container(
253261
node.destroy_client(load_node_client)
254262

255263

256-
def unload_component_from_container(*, node, remote_container_node_name, component_uids):
264+
def unload_component_from_container(
265+
*, node, remote_container_node_name, component_uids, timeout=None):
257266
"""
258267
Unload a component from a running container synchronously.
259268
260269
:param node: an `rclpy.Node` instance
261270
:param remote_container_node_name: of the container node to unload the component from
262271
:param component_uids: list of unique IDs of the components to be unloaded
272+
:param timeout: maximum time to wait for response in seconds (default: waits indefinitely)
263273
"""
264274
unload_node_client = node.create_client(
265275
composition_interfaces.srv.UnloadNode,
@@ -274,7 +284,11 @@ def unload_component_from_container(*, node, remote_container_node_name, compone
274284
request = composition_interfaces.srv.UnloadNode.Request()
275285
request.unique_id = uid
276286
future = unload_node_client.call_async(request)
277-
rclpy.spin_until_future_complete(node, future)
287+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
288+
if not future.done():
289+
raise RuntimeError(
290+
'Timed out waiting for unload_node response from '
291+
f'{remote_container_node_name!r} container (timeout: {timeout}s)')
278292
response = future.result()
279293
yield uid, not response.success, response.error_message
280294
finally:

ros2component/ros2component/verb/list.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ def add_arguments(self, parser, cli_name):
3636
parser.add_argument(
3737
'--containers-only', action='store_true',
3838
help='List found containers nodes only')
39+
parser.add_argument(
40+
'--timeout', metavar='N', type=float, default=5.0,
41+
help='Maximum time to wait for listing components in seconds '
42+
'(default: %(default)s)')
3943

4044
def main(self, *, args):
4145
with NodeStrategy(args) as node:
@@ -47,7 +51,8 @@ def main(self, *, args):
4751
return "Unable to find container node '" + args.container_node_name + "'"
4852
if not args.containers_only:
4953
ok, outcome = get_components_in_container(
50-
node=node, remote_container_node_name=args.container_node_name
54+
node=node, remote_container_node_name=args.container_node_name,
55+
timeout=args.timeout
5156
)
5257
if not ok:
5358
return f'{outcome} when listing components in {args.container_node_name}'
@@ -56,9 +61,12 @@ def main(self, *, args):
5661
f'{component.uid} {component.name}' for component in outcome
5762
], sep='\n')
5863
else:
59-
results = get_components_in_containers(node=node, remote_containers_node_names=[
60-
n.full_name for n in container_node_names
61-
])
64+
results = get_components_in_containers(
65+
node=node, remote_containers_node_names=[
66+
n.full_name for n in container_node_names
67+
],
68+
timeout=args.timeout
69+
)
6270
for container_node_name, (ok, outcome) in results.items():
6371
print(container_node_name)
6472
if not args.containers_only:

ros2component/ros2component/verb/load.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ def add_arguments(self, parser, cli_name):
3636
add_component_arguments(parser)
3737
parser.add_argument(
3838
'-q', '--quiet', action='store_true', default=False,
39-
help='Only print component unique IDs and names'
40-
)
39+
help='Only print component unique IDs and names')
40+
parser.add_argument(
41+
'--timeout', metavar='N', type=float,
42+
help='Maximum time to wait for loading component in seconds '
43+
'(default: waits indefinitely)')
4144

4245
def main(self, *, args):
4346
with NodeStrategy(args) as node:
@@ -51,7 +54,8 @@ def main(self, *, args):
5154
package_name=args.package_name, plugin_name=args.plugin_name,
5255
node_name=args.node_name, node_namespace=args.node_namespace,
5356
log_level=args.log_level, remap_rules=args.remap_rules,
54-
parameters=args.parameters, extra_arguments=args.extra_arguments
57+
parameters=args.parameters, extra_arguments=args.extra_arguments,
58+
timeout=args.timeout
5559
)
5660
if not args.quiet:
5761
print("Loaded component {} into '{}' container node as '{}'".format(

ros2component/ros2component/verb/unload.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ def add_arguments(self, parser, cli_name):
3737
)
3838
parser.add_argument(
3939
'-q', '--quiet', action='store_true', default=False,
40-
help='Only print component unique IDs'
41-
)
40+
help='Only print component unique IDs')
41+
parser.add_argument(
42+
'--timeout', metavar='N', type=float,
43+
help='Maximum time to wait for unloading component in seconds '
44+
'(default: waits indefinitely)')
4245

4346
def main(self, *, args):
4447
with NodeStrategy(args) as node:
@@ -49,7 +52,7 @@ def main(self, *, args):
4952
return "Unable to find container node '" + args.container_node_name + "'"
5053
for uid, error, reason in unload_component_from_container(
5154
node=node, remote_container_node_name=args.container_node_name,
52-
component_uids=args.component_uid
55+
component_uids=args.component_uid, timeout=args.timeout
5356
):
5457
if error:
5558
return "Failed to unload component {} from '{}' container node\n {}".format(

ros2lifecycle/ros2lifecycle/api/__init__.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _has_lifecycle(node_name, service_names_and_types):
4343
return False
4444

4545

46-
def call_get_states(*, node, node_names):
46+
def call_get_states(*, node, node_names, timeout=None):
4747
clients = {}
4848
futures = {}
4949
# create clients
@@ -67,7 +67,10 @@ def call_get_states(*, node, node_names):
6767

6868
# wait for all responses
6969
for future in futures.values():
70-
rclpy.spin_until_future_complete(node, future)
70+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
71+
if not future.done():
72+
raise RuntimeError(
73+
f'Timed out waiting for get_state response (timeout: {timeout}s)')
7174

7275
# return current state or exception for each node
7376
states = {}
@@ -80,15 +83,15 @@ def call_get_states(*, node, node_names):
8083
return states
8184

8285

83-
def call_get_available_transitions(*, node, states):
84-
return _call_get_transitions(node, states, 'get_available_transitions')
86+
def call_get_available_transitions(*, node, states, timeout=None):
87+
return _call_get_transitions(node, states, 'get_available_transitions', timeout)
8588

8689

87-
def call_get_transition_graph(*, node, states):
88-
return _call_get_transitions(node, states, 'get_transition_graph')
90+
def call_get_transition_graph(*, node, states, timeout=None):
91+
return _call_get_transitions(node, states, 'get_transition_graph', timeout)
8992

9093

91-
def _call_get_transitions(node, states, service_name):
94+
def _call_get_transitions(node, states, service_name, timeout=None):
9295
clients = {}
9396
futures = {}
9497
# create clients
@@ -112,7 +115,10 @@ def _call_get_transitions(node, states, service_name):
112115

113116
# wait for all responses
114117
for future in futures.values():
115-
rclpy.spin_until_future_complete(node, future)
118+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
119+
if not future.done():
120+
raise RuntimeError(
121+
f'Timed out waiting for {service_name} response (timeout: {timeout}s)')
116122

117123
# return transitions from current state or exception for each node
118124
transitions = {}
@@ -132,7 +138,7 @@ def _call_get_transitions(node, states, service_name):
132138
return transitions
133139

134140

135-
def call_change_states(*, node, transitions):
141+
def call_change_states(*, node, transitions, timeout=None):
136142
clients = {}
137143
futures = {}
138144
# create clients
@@ -157,7 +163,10 @@ def call_change_states(*, node, transitions):
157163

158164
# wait for all responses
159165
for future in futures.values():
160-
rclpy.spin_until_future_complete(node, future)
166+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout)
167+
if not future.done():
168+
raise RuntimeError(
169+
f'Timed out waiting for change_state response (timeout: {timeout}s)')
161170

162171
# return success flag or exception for each node
163172
results = {}

ros2lifecycle/ros2lifecycle/verb/get.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def add_arguments(self, parser, cli_name): # noqa: D102
3737
parser.add_argument(
3838
'--include-hidden-nodes', action='store_true',
3939
help='Consider hidden nodes as well')
40+
parser.add_argument(
41+
'--timeout', metavar='N', type=float,
42+
help='Maximum time to wait for response in seconds '
43+
'(default: waits indefinitely)')
4044

4145
def main(self, *, args): # noqa: D102
4246
with NodeStrategy(args) as node:
@@ -55,7 +59,8 @@ def main(self, *, args): # noqa: D102
5559
with DirectNode(args) as node:
5660
# Process and output each node's state immediately upon receipt
5761
for query_node_name in nodes_to_query:
58-
state = call_get_states(node=node, node_names=[query_node_name])
62+
state = call_get_states(
63+
node=node, node_names=[query_node_name], timeout=args.timeout)
5964
state = state.get(query_node_name)
6065

6166
if isinstance(state, Exception):

ros2lifecycle/ros2lifecycle/verb/list.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def add_arguments(self, parser, cli_name): # noqa: D102
4040
parser.add_argument(
4141
'-a', '--all', action='store_true',
4242
help='Display all existing transitions')
43+
parser.add_argument(
44+
'--timeout', metavar='N', type=float,
45+
help='Maximum time to wait for response in seconds '
46+
'(default: waits indefinitely)')
4347

4448
def main(self, *, args): # noqa: D102
4549
with NodeStrategy(args) as node:
@@ -53,10 +57,10 @@ def main(self, *, args): # noqa: D102
5357
with DirectNode(args) as node:
5458
if args.all:
5559
transitions = call_get_transition_graph(
56-
node=node, states={node_name: None})
60+
node=node, states={node_name: None}, timeout=args.timeout)
5761
else:
5862
transitions = call_get_available_transitions(
59-
node=node, states={node_name: None})
63+
node=node, states={node_name: None}, timeout=args.timeout)
6064
transitions = transitions[node_name]
6165
if isinstance(transitions, Exception):
6266
return 'Exception while calling service of node ' \

ros2lifecycle/ros2lifecycle/verb/set.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ def add_arguments(self, parser, cli_name): # noqa: D102
4141
help='Consider hidden nodes as well')
4242
parser.add_argument(
4343
'transition', help='The lifecycle transition')
44+
parser.add_argument(
45+
'--timeout', metavar='N', type=float,
46+
help='Maximum time to wait for response in seconds '
47+
'(default: waits indefinitely)')
4448

4549
def main(self, *, args): # noqa: D102
4650
with NodeStrategy(args) as node:
@@ -53,7 +57,7 @@ def main(self, *, args): # noqa: D102
5357

5458
with DirectNode(args) as node:
5559
transitions = call_get_available_transitions(
56-
node=node, states={node_name: None})
60+
node=node, states={node_name: None}, timeout=args.timeout)
5761
transitions = transitions[node_name]
5862
if isinstance(transitions, Exception):
5963
return 'Exception while calling service of node ' \
@@ -75,7 +79,7 @@ def main(self, *, args): # noqa: D102
7579
for t in transitions)
7680

7781
results = call_change_states(
78-
node=node, transitions={node_name: transition})
82+
node=node, transitions={node_name: transition}, timeout=args.timeout)
7983
result = results[node_name]
8084

8185
# output response

0 commit comments

Comments
 (0)