Skip to content

Commit f31cd91

Browse files
authored
add timeout option for ros2param to find node. (#802)
Signed-off-by: Tomoya Fujita <[email protected]>
1 parent ad7cd84 commit f31cd91

File tree

9 files changed

+71
-48
lines changed

9 files changed

+71
-48
lines changed

ros2cli/ros2cli/helpers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ def wait_for(predicate, timeout, period=0.1):
3939
deadline = time.time() + timeout
4040
while not predicate():
4141
if time.time() > deadline:
42-
break
42+
return predicate()
4343
time.sleep(period)
44-
return predicate()
45-
44+
return True
4645

4746
def bind(func, *args, **kwargs):
4847
"""

ros2node/ros2node/api/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import time
16+
1517
from collections import namedtuple
1618
from typing import Any
1719
from typing import List
1820

1921
from rclpy.node import HIDDEN_NODE_PREFIX
22+
from ros2cli.helpers import wait_for
2023
from ros2cli.node.strategy import NodeStrategy
2124

2225
INFO_NONUNIQUE_WARNING_TEMPLATE = (
@@ -71,6 +74,15 @@ def get_node_names(*, node, include_hidden_nodes=False):
7174
]
7275

7376

77+
def wait_for_node(node, node_name, include_hidden_nodes=False, timeout=1.0) -> bool:
78+
def node_available():
79+
node_names = get_node_names(
80+
node=node, include_hidden_nodes=include_hidden_nodes)
81+
return True if node_name in {n.full_name for n in node_names} else False
82+
83+
return wait_for(node_available, timeout)
84+
85+
7486
def get_topics(remote_node_name, func, *, include_hidden_topics=False):
7587
node = parse_node_name(remote_node_name)
7688
names_and_types = func(node.name, node.namespace)

ros2param/ros2param/verb/delete.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
from ros2cli.node.strategy import add_arguments
2222
from ros2cli.node.strategy import NodeStrategy
2323
from ros2node.api import get_absolute_node_name
24-
from ros2node.api import get_node_names
2524
from ros2node.api import NodeNameCompleter
25+
from ros2node.api import wait_for_node
2626
from ros2param.api import call_set_parameters
2727
from ros2param.api import ParameterNameCompleter
2828
from ros2param.verb import VerbExtension
@@ -43,15 +43,15 @@ def add_arguments(self, parser, cli_name): # noqa: D102
4343
arg = parser.add_argument(
4444
'parameter_name', help='Name of the parameter')
4545
arg.completer = ParameterNameCompleter()
46+
parser.add_argument(
47+
'--timeout', metavar='N', type=int, default=1,
48+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
4649

4750
def main(self, *, args): # noqa: D102
48-
with NodeStrategy(args) as node:
49-
node_names = get_node_names(
50-
node=node, include_hidden_nodes=args.include_hidden_nodes)
51-
5251
node_name = get_absolute_node_name(args.node_name)
53-
if node_name not in [n.full_name for n in node_names]:
54-
return 'Node not found'
52+
with NodeStrategy(args) as node:
53+
if not wait_for_node(node, node_name, args.include_hidden_nodes, args.timeout):
54+
return 'Node not found'
5555

5656
with DirectNode(args) as node:
5757
parameter = Parameter()

ros2param/ros2param/verb/describe.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from ros2cli.node.strategy import add_arguments
1717
from ros2cli.node.strategy import NodeStrategy
1818
from ros2node.api import get_absolute_node_name
19-
from ros2node.api import get_node_names
2019
from ros2node.api import NodeNameCompleter
20+
from ros2node.api import wait_for_node
2121
from ros2param.api import call_describe_parameters
2222
from ros2param.api import get_parameter_type_string
2323
from ros2param.api import ParameterNameCompleter
@@ -38,16 +38,16 @@ def add_arguments(self, parser, cli_name): # noqa: D102
3838
help='Consider hidden nodes as well')
3939
arg = parser.add_argument(
4040
'parameter_names', nargs='+', help='Names of the parameters')
41+
parser.add_argument(
42+
'--timeout', metavar='N', type=int, default=1,
43+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
4144
arg.completer = ParameterNameCompleter()
4245

4346
def main(self, *, args): # noqa: D102
44-
with NodeStrategy(args) as node:
45-
node_names = get_node_names(
46-
node=node, include_hidden_nodes=args.include_hidden_nodes)
47-
4847
node_name = get_absolute_node_name(args.node_name)
49-
if node_name not in {n.full_name for n in node_names}:
50-
return 'Node not found'
48+
with NodeStrategy(args) as node:
49+
if not wait_for_node(node, node_name, args.include_hidden_nodes, args.timeout):
50+
return 'Node not found'
5151

5252
with DirectNode(args) as node:
5353
response = call_describe_parameters(

ros2param/ros2param/verb/dump.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
from ros2cli.node.strategy import add_arguments
2121
from ros2cli.node.strategy import NodeStrategy
2222
from ros2node.api import get_absolute_node_name
23-
from ros2node.api import get_node_names
2423
from ros2node.api import NodeNameCompleter
2524
from ros2node.api import parse_node_name
25+
from ros2node.api import wait_for_node
2626

2727
from ros2param.api import call_get_parameters
2828
from ros2param.api import call_list_parameters
@@ -50,6 +50,9 @@ def add_arguments(self, parser, cli_name): # noqa: D102
5050
parser.add_argument(
5151
'--print', action='store_true',
5252
help='DEPRECATED: Does nothing.')
53+
parser.add_argument(
54+
'--timeout', metavar='N', type=int, default=1,
55+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
5356

5457
@staticmethod
5558
def get_parameter_values(node, node_name, params):
@@ -74,16 +77,13 @@ def insert_dict(self, dictionary, key, value):
7477
dictionary[key] = value
7578

7679
def main(self, *, args): # noqa: D102
77-
78-
with NodeStrategy(args) as node:
79-
node_names = get_node_names(node=node, include_hidden_nodes=args.include_hidden_nodes)
80-
8180
absolute_node_name = get_absolute_node_name(args.node_name)
82-
node_name = parse_node_name(absolute_node_name)
83-
if absolute_node_name:
84-
if absolute_node_name not in [n.full_name for n in node_names]:
81+
with NodeStrategy(args) as node:
82+
if not wait_for_node(node, absolute_node_name,
83+
args.include_hidden_nodes, args.timeout):
8584
return 'Node not found'
8685

86+
node_name = parse_node_name(absolute_node_name)
8787
if not os.path.isdir(args.output_dir):
8888
raise RuntimeError(
8989
f"'{args.output_dir}' is not a valid directory.")

ros2param/ros2param/verb/get.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from ros2cli.node.strategy import add_arguments
1818
from ros2cli.node.strategy import NodeStrategy
1919
from ros2node.api import get_absolute_node_name
20-
from ros2node.api import get_node_names
2120
from ros2node.api import NodeNameCompleter
21+
from ros2node.api import wait_for_node
2222
from ros2param.api import call_get_parameters
2323
from ros2param.api import ParameterNameCompleter
2424
from ros2param.verb import VerbExtension
@@ -42,15 +42,15 @@ def add_arguments(self, parser, cli_name): # noqa: D102
4242
parser.add_argument(
4343
'--hide-type', action='store_true',
4444
help='Hide the type information')
45+
parser.add_argument(
46+
'--timeout', metavar='N', type=int, default=1,
47+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
4548

4649
def main(self, *, args): # noqa: D102
47-
with NodeStrategy(args) as node:
48-
node_names = get_node_names(
49-
node=node, include_hidden_nodes=args.include_hidden_nodes)
50-
5150
node_name = get_absolute_node_name(args.node_name)
52-
if node_name not in {n.full_name for n in node_names}:
53-
return 'Node not found'
51+
with NodeStrategy(args) as node:
52+
if not wait_for_node(node, node_name, args.include_hidden_nodes, args.timeout):
53+
return 'Node not found'
5454

5555
with DirectNode(args) as node:
5656
response = call_get_parameters(

ros2param/ros2param/verb/load.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from ros2cli.node.strategy import add_arguments
1717
from ros2cli.node.strategy import NodeStrategy
1818
from ros2node.api import get_absolute_node_name
19-
from ros2node.api import get_node_names
2019
from ros2node.api import NodeNameCompleter
20+
from ros2node.api import wait_for_node
2121
from ros2param.api import load_parameter_file
2222
from ros2param.verb import VerbExtension
2323

@@ -39,15 +39,15 @@ def add_arguments(self, parser, cli_name): # noqa: D102
3939
parser.add_argument(
4040
'--no-use-wildcard', action='store_true',
4141
help="Do not load parameters in the '/**' namespace into the node")
42+
parser.add_argument(
43+
'--timeout', metavar='N', type=int, default=1,
44+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
4245

4346
def main(self, *, args): # noqa: D102
44-
with NodeStrategy(args) as node:
45-
node_names = get_node_names(
46-
node=node, include_hidden_nodes=args.include_hidden_nodes)
47-
4847
node_name = get_absolute_node_name(args.node_name)
49-
if node_name not in {n.full_name for n in node_names}:
50-
return 'Node not found'
48+
with NodeStrategy(args) as node:
49+
if not wait_for_node(node, node_name, args.include_hidden_nodes, args.timeout):
50+
return 'Node not found'
5151

5252
with DirectNode(args) as node:
5353
load_parameter_file(node=node, node_name=node_name, parameter_file=args.parameter_file,

ros2param/ros2param/verb/set.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from ros2cli.node.strategy import add_arguments
2121
from ros2cli.node.strategy import NodeStrategy
2222
from ros2node.api import get_absolute_node_name
23-
from ros2node.api import get_node_names
2423
from ros2node.api import NodeNameCompleter
24+
from ros2node.api import wait_for_node
2525

2626
from ros2param.api import call_set_parameters
2727
from ros2param.api import ParameterNameCompleter
@@ -45,15 +45,15 @@ def add_arguments(self, parser, cli_name): # noqa: D102
4545
arg.completer = ParameterNameCompleter()
4646
parser.add_argument(
4747
'value', help='Value of the parameter')
48+
parser.add_argument(
49+
'--timeout', metavar='N', type=int, default=1,
50+
help='Wait for N seconds until node becomes available (default %(default)s sec)')
4851

4952
def main(self, *, args): # noqa: D102
50-
with NodeStrategy(args) as node:
51-
node_names = get_node_names(
52-
node=node, include_hidden_nodes=args.include_hidden_nodes)
53-
5453
node_name = get_absolute_node_name(args.node_name)
55-
if node_name not in {n.full_name for n in node_names}:
56-
return 'Node not found'
54+
with NodeStrategy(args) as node:
55+
if not wait_for_node(node, node_name, args.include_hidden_nodes, args.timeout):
56+
return 'Node not found'
5757

5858
with DirectNode(args) as node:
5959
parameter = Parameter()

ros2param/test/test_verb_load.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,23 @@ def test_verb_load_invalid_path(self):
269269
strict=False
270270
)
271271

272+
def test_verb_load_timeout(self):
273+
with self.launch_param_load_command(
274+
arguments=['invalid_node', 'invalid_path', '--timeout', '2']
275+
) as param_load_command:
276+
assert param_load_command.wait_for_shutdown(timeout=TEST_TIMEOUT)
277+
assert param_load_command.exit_code != launch_testing.asserts.EXIT_OK
278+
assert launch_testing.tools.expect_output(
279+
expected_lines=['Node not found'],
280+
text=param_load_command.output,
281+
strict=False
282+
)
283+
272284
def test_verb_load(self):
273285
with tempfile.TemporaryDirectory() as tmpdir:
274286
filepath = self._write_param_file(tmpdir, 'params.yaml')
275287
with self.launch_param_load_command(
276-
arguments=[f'{TEST_NAMESPACE}/{TEST_NODE}', filepath]
288+
arguments=[f'{TEST_NAMESPACE}/{TEST_NODE}', filepath, '--timeout', '3']
277289
) as param_load_command:
278290
assert param_load_command.wait_for_shutdown(timeout=TEST_TIMEOUT)
279291
assert param_load_command.exit_code == launch_testing.asserts.EXIT_OK
@@ -300,7 +312,7 @@ def test_verb_load_wildcard(self):
300312
filepath = self._write_param_file(tmpdir, 'params.yaml', INPUT_WILDCARD_PARAMETER_FILE)
301313
with self.launch_param_load_command(
302314
arguments=[f'{TEST_NAMESPACE}/{TEST_NODE}', filepath,
303-
'--no-use-wildcard']
315+
'--no-use-wildcard', '--timeout', '3']
304316
) as param_load_command:
305317
assert param_load_command.wait_for_shutdown(timeout=TEST_TIMEOUT)
306318
assert param_load_command.exit_code != launch_testing.asserts.EXIT_OK

0 commit comments

Comments
 (0)