Skip to content
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

[ROS-O] required patches on current systems #2297

Open
wants to merge 12 commits into
base: noetic-devel
Choose a base branch
from
Prev Previous commit
Next Next commit
roslaunch: return exit code of a required process (#2082)
Resolves #919
  • Loading branch information
lucasw authored and rhaschke committed Oct 15, 2024
commit 27027a6a4636ccd5c69ed9b5e57148c3e4949b1b
41 changes: 41 additions & 0 deletions test/test_roslaunch/test/exit_with_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python
# Software License Agreement (BSD License)
#
# Copyright (c) 2014, The Johns Hopkins University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import rospy
import sys


if __name__ == '__main__':
rospy.init_node('exit_with_code', anonymous=True)
exit_code = rospy.get_param("~exit_code", 0)
sys.exit(exit_code)
1 change: 1 addition & 0 deletions test/test_roslaunch/test/roslaunch.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<launch>
<test test-name="roslaunch_command_line_online" pkg="test_roslaunch" type="test_roslaunch_command_line_online.py" />
<test test-name="roslaunch_ros_args" pkg="test_roslaunch" type="test_roslaunch_ros_args.py" />
<test test-name="roslaunch_exit_code" pkg="test_roslaunch" type="test_roslaunch_exit_code.py" />
</launch>
72 changes: 72 additions & 0 deletions test/test_roslaunch/test/test_roslaunch_exit_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python
# Software License Agreement (BSD License)
#
# Copyright (c) 2009, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import sys
import unittest
from subprocess import CalledProcessError, check_call

import rostest

PKG = 'roslaunch'
NAME = 'test_roslaunch_exit_code'


class TestRoslaunchExit(unittest.TestCase):

def setUp(self):
self.vals = set()
self.msgs = {}

def test_roslaunch(self):
# network is initialized
cmd = 'roslaunch'
exit_code = 37

check_call([cmd, 'test_roslaunch', 'exit.launch',
'exit_code:={}'.format(0), 'required:=true'])

try:
check_call([cmd, 'test_roslaunch', 'exit.launch',
'exit_code:={}'.format(exit_code), 'required:=true'])
except CalledProcessError as ex:
if ex.returncode != exit_code:
raise(ex)

check_call([cmd, 'test_roslaunch', 'exit.launch',
'exit_code:={}'.format(0), 'required:=false'])
check_call([cmd, 'test_roslaunch', 'exit.launch',
'exit_code:={}'.format(exit_code), 'required:=false'])


if __name__ == '__main__':
rostest.run(PKG, NAME, TestRoslaunchExit, sys.argv)
10 changes: 10 additions & 0 deletions test/test_roslaunch/test/xml/exit.launch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<launch>
<arg name="required" default="true"
doc="make the node here cause the launch to exit with the exit code of the required process" />
<arg name="exit_code" default="0" doc="exit with this code" />
<!-- roslaunch file that loads no nodes, and thus should exit immediately -->
<node name="exit_with_code" pkg="test_roslaunch" type="exit_with_code.py" required="$(arg required)" >
<param name="exit_code" value="$(arg exit_code)" />
</node>
</launch>
7 changes: 6 additions & 1 deletion tools/roslaunch/src/roslaunch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def handle_exception(roslaunch_core, logger, msg, e):
def main(argv=sys.argv):
options = None
logger = None
exit_code = 0
try:
from . import rlutil
parser = _get_optparse()
Expand Down Expand Up @@ -303,7 +304,7 @@ def main(argv=sys.argv):
logger.info("roslaunch env is %s"%os.environ)

if options.child_name:
logger.info('starting in child mode')
logger.info('starting {} in child mode'.format(options.child_name))

# This is a roslaunch child, spin up client server.
# client spins up an XML-RPC server that waits for
Expand All @@ -313,6 +314,8 @@ def main(argv=sys.argv):
sigint_timeout=options.sigint_timeout,
sigterm_timeout=options.sigterm_timeout)
c.run()
exit_code = c.exit_code
logger.debug('child node {} done {}'.format(options.child_name, exit_code))
else:
logger.info('starting in server mode')

Expand Down Expand Up @@ -346,6 +349,7 @@ def main(argv=sys.argv):
sigterm_timeout=options.sigterm_timeout)
p.start()
p.spin()
exit_code = p.exit_code

except RLException as e:
handle_exception(roslaunch_core, logger, "RLException: ", e)
Expand All @@ -363,6 +367,7 @@ def main(argv=sys.argv):
try: os.unlink(options.pid_fn)
except os.error: pass

sys.exit(exit_code)

if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions tools/roslaunch/src/roslaunch/child.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def __init__(self, run_id, name, server_uri, sigint_timeout=DEFAULT_TIMEOUT_SIGI
self.pm = None
self.sigint_timeout = sigint_timeout
self.sigterm_timeout = sigterm_timeout
self.exit_code = 0

roslaunch.pmon._init_signal_handlers()

Expand Down Expand Up @@ -126,10 +127,12 @@ def run(self):
if self.pm:
self.pm.shutdown()
self.pm.join()
self.exit_code = self.pm.exit_code
if self.child_server:
self.child_server.shutdown('roslaunch child complete')

def shutdown(self):
if self.pm:
self.pm.shutdown()
self.pm.join()
self.exit_code = self.pm.exit_code
2 changes: 2 additions & 0 deletions tools/roslaunch/src/roslaunch/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ def __init__(self, run_id, config, server_uri=None, pmon=None, is_core=False, re
self.is_child = is_child
self.is_core = is_core
self.is_rostest = is_rostest
self.exit_code = 0
self.num_workers = num_workers
self.timeout = timeout
self.master_logger_level = master_logger_level
Expand Down Expand Up @@ -629,6 +630,7 @@ def stop(self):
printlog("shutting down processing monitor...")
self.logger.info("shutting down processing monitor %s"%self.pm)
self.pm.shutdown()
self.exit_code = self.pm.exit_code
self.pm.join()
self.pm = None
printlog("... shutting down processing monitor complete")
Expand Down
2 changes: 2 additions & 0 deletions tools/roslaunch/src/roslaunch/parent.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def __init__(self, run_id, roslaunch_files, is_core=False, port=None, local_only
self._shutting_down = False

self.config = self.runner = self.server = self.pm = self.remote_runner = None
self.exit_code = 0

def _load_config(self):
self.config = roslaunch.config.load_config_default(self.roslaunch_files, self.port,
Expand Down Expand Up @@ -286,6 +287,7 @@ def _stop_infrastructure(self):
if self.pm:
self.pm.shutdown()
self.pm.join()
self.exit_code = self.pm.exit_code

def start(self, auto_terminate=True):
"""
Expand Down
7 changes: 7 additions & 0 deletions tools/roslaunch/src/roslaunch/pmon.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ def __init__(self, name="ProcessMonitor"):
self.procs = []
self.plock = RLock()
self.is_shutdown = False
self.exit_code = 0
self.done = False
self.daemon = True
self.reacquire_signals = set()
Expand Down Expand Up @@ -566,6 +567,12 @@ def _run(self):
if p.required:
printerrlog('='*80+"REQUIRED process [%s] has died!\n%s\nInitiating shutdown!\n"%(p.name, exit_code_str)+'='*80)
self.is_shutdown = True
if p.exit_code != 0:
msg = 'Going to exit due to non-zero exit code ({})'.format(p.exit_code)
msg += 'of required process {}'.format(p.name)

printerrlog(msg)
self.exit_code = p.exit_code
elif not p in respawn:
if p.exit_code:
printerrlog("[%s] %s"%(p.name, exit_code_str))
Expand Down
1 change: 1 addition & 0 deletions tools/roslaunch/test/unit/test_roslaunch_child.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self):
self.core_procs = []
self.procs = []
self.listeners = []
self.exit_code = 0

def join(self, timeout=0):
pass
Expand Down
1 change: 1 addition & 0 deletions tools/roslaunch/test/unit/test_roslaunch_parent.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __init__(self):
self.procs = []
self.listeners = []
self.is_shutdown = False
self.exit_code = 0

def join(self, timeout=0):
pass
Expand Down