app_manager.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2011, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 #
33 # Revision $Id: app_manager.py 14948 2011-09-07 19:25:54Z pratkanis $
34 
35 # author: leibs
36 import sys
37 import os
38 
39 if sys.version_info[0] == 3:
40  import _thread as thread # python3 renamed from thread to _thread
41 else:
42  import thread
43 
44 import time
45 import yaml
46 
47 import traceback
48 
49 import rosgraph.names
50 import rospy
51 import roslib
52 
53 import roslaunch.config
54 import roslaunch.core
55 import roslaunch.parent
56 import roslaunch.pmon
57 import roslaunch.xmlloader
58 
59 import roslaunch.loader
60 from std_srvs.srv import Empty, EmptyResponse
61 
62 from .app import AppDefinition, find_resource, load_AppDefinition_by_name
63 from .exceptions import LaunchException, AppException, InvalidAppException, NotFoundException
64 from .master_sync import MasterSync
65 from .msg import App, AppList, StatusCodes, AppStatus, AppInstallationState, ExchangeApp
66 from .srv import StartApp, StopApp, ListApps, ListAppsResponse, StartAppResponse, StopAppResponse, InstallApp, UninstallApp, GetInstallationState, UninstallAppResponse, InstallAppResponse, GetInstallationStateResponse, GetAppDetails, GetAppDetailsResponse
67 
68 # for profiling
69 # import cProfile, pstats
70 # from io import BytesIO as StringIO
71 
73  roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False,
74  assign_machines=True, ignore_unset_args=False
75 ):
76  config = roslaunch.config.ROSLaunchConfig()
77  if port:
78  config.master.uri = rosgraph.network.create_local_xmlrpc_uri(port)
79 
80  loader = loader or roslaunch.xmlloader.XmlLoader()
81  loader.ignore_unset_args = ignore_unset_args
82 
83  # load the roscore file first. we currently have
84  # last-declaration wins rules. roscore is just a
85  # roslaunch file with special load semantics
86  roslaunch.config.load_roscore(loader, config, verbose=verbose)
87 
88  # load the roslaunch_files into the config
89  for f in roslaunch_files:
90  if isinstance(f, tuple):
91  f, args = f
92  else:
93  args = None
94  try:
95  rospy.loginfo('loading config file %s' % f)
96  loader.load(f, config, argv=args, verbose=verbose)
97  except roslaunch.xmlloader.XmlParseException as e:
98  raise roslaunch.core.RLException(e)
99  except roslaunch.loader.LoadException as e:
100  raise roslaunch.core.RLException(e)
101  # we need this for the hardware test systems, which builds up
102  # roslaunch launch files in memory
103  if roslaunch_strs:
104  for launch_str in roslaunch_strs:
105  try:
106  rospy.loginfo('loading config file from string')
107  loader.load_string(launch_str, config)
108  except roslaunch.xmlloader.XmlParseException as e:
109  raise roslaunch.core.RLException(
110  'Launch string: %s\nException: %s' % (launch_str, e))
111  except roslaunch.loader.LoadException as e:
112  raise roslaunch.core.RLException(
113  'Launch string: %s\nException: %s' % (launch_str, e))
114  # choose machines for the nodes
115  if assign_machines:
116  config.assign_machines()
117  return config
118 
119 
120 # overwrite load_config_default function for kinetic
121 # see: https://github.com/ros/ros_comm/pull/1115
122 roslaunch.config.load_config_default = _load_config_default
123 
124 
125 class AppManager(object):
126 
127  def __init__(
128  self, robot_name, interface_master, app_list,
129  exchange, plugins=None, enable_app_replacement=True,
130  enable_topic_remapping=True,
131  sigint_timeout=15.0, sigterm_timeout=2.0,
132  ):
133  self._robot_name = robot_name
134  self._interface_master = interface_master
135  self._app_list = app_list
137  self._exchange = exchange
138  self._plugins = plugins
139  self._enable_app_replacement = enable_app_replacement
140  self._enable_topic_remapping = enable_topic_remapping
141  self._sigint_timeout = sigint_timeout
142  self._sigterm_timeout = sigterm_timeout
143 
144  rospy.loginfo("Starting app manager for %s"%self._robot_name)
145 
146  self._app_interface = self.scoped_name('application')
147 
148  # note: we publish into the application namespace
149  self._status_pub = rospy.Publisher(
150  self.scoped_name('application/app_status'), AppStatus,
151  latch=True, queue_size=1)
152  self._list_apps_pub = rospy.Publisher(
153  self.scoped_name('app_list'), AppList,
154  latch=True, queue_size=1)
155 
156  self._list_apps_srv = rospy.Service(self.scoped_name('list_apps'), ListApps, self.handle_list_apps)
157  self._start_app_srv = rospy.Service(self.scoped_name('start_app'), StartApp, self.handle_start_app)
158  self._stop_app_srv = rospy.Service(self.scoped_name('stop_app'), StopApp, self.handle_stop_app)
159  self._reload_app_list_srv = rospy.Service(self.scoped_name('reload_app_list'), Empty, self.handle_reload_app_list)
160  if (self._exchange):
161  self._exchange_list_apps_pub = rospy.Publisher(self.scoped_name('exchange_app_list'), AppInstallationState, latch=True)
162  self._list_exchange_apps_srv = rospy.Service(self.scoped_name('list_exchange_apps'), GetInstallationState, self.handle_list_exchange_apps)
163  self._get_app_details_srv = rospy.Service(self.scoped_name('get_app_details'), GetAppDetails, self.handle_get_app_details)
164  self._install_app_srv = rospy.Service(self.scoped_name('install_app'), InstallApp, self.handle_install_app)
165  self._uninstall_app_srv = rospy.Service(self.scoped_name('uninstall_app'), UninstallApp, self.handle_uninstall_app)
166 
167  pub_names = [x.resolved_name for x in [self._list_apps_pub, self._status_pub, self._exchange_list_apps_pub]]
168  service_names = [x.resolved_name for x in [self._list_apps_srv, self._start_app_srv, self._stop_app_srv, self._get_app_details_srv, self._list_exchange_apps_srv, self._install_app_srv, self._uninstall_app_srv]]
169  else:
170  pub_names = [x.resolved_name for x in [self._list_apps_pub, self._status_pub]]
171  service_names = [x.resolved_name for x in [self._list_apps_srv, self._start_app_srv, self._stop_app_srv]]
172 
174  local_service_names=service_names,
175  local_pub_names=pub_names)
176 
177  self._launch = None
178  self._plugin_launch = None
179  self._interface_sync = None
180  self._exit_code = None
181  self._stopped = None
182  self._stopping = None
183  self._current_process = None
184  self._timeout = None
185  self._current_plugins = None
187  self._plugin_context = None
188  self._plugin_insts = None
189  self._start_time = None
190 
191  roslaunch.pmon._init_signal_handlers()
192 
193  if (self._exchange):
194  self._exchange.update_local()
195 
196  # for time profiling
197  # time profiling is commented out because it will slow down.
198  # comment in when you debug it
199  # start_time = time.time()
200  # pr = cProfile.Profile()
201  # pr.enable()
202  self._app_list.update()
204  self.publish_list_apps()
205  # pr.disable()
206  # s = StringIO()
207  # sortby = 'cumulative'
208  # ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
209  # ps.print_stats()
210  # print(s.getvalue())
211  # end_time = time.time()
212  # rospy.logerr('total time: {}'.format(end_time - start_time))
213 
214  # show_summary keyword is added in melodic
215  # removing for kinetic compability
216  rospy.loginfo("Initializing default launcher")
217  try:
218  self._default_launch = roslaunch.parent.ROSLaunchParent(
219  rospy.get_param("/run_id"), [], is_core=False,
220  sigint_timeout=self._sigint_timeout,
221  sigterm_timeout=self._sigterm_timeout)
222  except TypeError:
223  # ROSLaunchParent() does not have sigint/sigterm_timeout argument
224  # if roslaunch < 1.14.13 or < 1.15.5
225  self._default_launch = roslaunch.parent.ROSLaunchParent(
226  rospy.get_param("/run_id"), [], is_core=False)
227  self._default_launch.start(auto_terminate=False)
228 
229  def shutdown(self):
230  if self._api_sync:
231  self._api_sync.stop()
232  if self._interface_sync:
233  self._interface_sync.stop()
234  self._stopped = True
235  self.__stop_current()
236 
237  def _get_current_app(self):
238  return self._current_app
239 
240  def _set_current_app(self, app, app_definition):
241  self._current_app = app
242  self._current_app_definition = app_definition
243 
244  if self._list_apps_pub:
245  if app is not None:
246  self._list_apps_pub.publish([app], self._app_list.get_app_list())
247  else:
248  self._list_apps_pub.publish([], self._app_list.get_app_list())
249 
250  def scoped_name(self, name):
251  return rosgraph.names.canonicalize_name('/%s/%s'%(self._robot_name, rospy.remap_name(name)))
252 
253  def handle_get_app_details(self, req):
254  return GetAppDetailsResponse(app=self._exchange.get_app_details(req.name))
255 
257  if (self._exchange == None):
258  return None
259  if (req.remote_update):
260  rospy.loginfo("UPDATE")
261  if (not self._exchange.update()):
262  return None
263  i_apps = self._exchange.get_installed_apps()
264  a_apps = self._exchange.get_available_apps()
265  return GetInstallationStateResponse(installed_apps=i_apps, available_apps=a_apps)
266 
267  def publish_list_apps(self):
268  if self._current_app:
269  self._list_apps_pub.publish([self._current_app], self._app_list.get_app_list())
270  else:
271  self._list_apps_pub.publish([], self._app_list.get_app_list())
272 
274  if (self._exchange == None):
275  return
276  i_apps = self._exchange.get_installed_apps()
277  a_apps = self._exchange.get_available_apps()
278  self._exchange_list_apps_pub.publish(i_apps, a_apps)
279 
280  def handle_install_app(self, req):
281  appname = req.name
282  if (self._exchange.install_app(appname)):
283  self._app_list.update()
284  self.publish_list_apps()
286  return InstallAppResponse(installed=True, message="app [%s] installed"%(appname))
287  else:
288  return InstallAppResponse(installed=False, message="app [%s] could not be installed"%(appname))
289 
290  def handle_uninstall_app(self, req):
291  appname = req.name
292  if (self._exchange.uninstall_app(appname)):
293  self._app_list.update()
294  self.publish_list_apps()
296  return UninstallAppResponse(uninstalled=True, message="app [%s] uninstalled"%(appname))
297  else:
298  return UninstallAppResponse(uninstalled=False, message="app [%s] could not be uninstalled"%(appname))
299 
300  def handle_list_apps(self, req):
301  rospy.loginfo("Listing apps")
302  current = self._current_app
303  if current:
304  running_apps = [current]
305  else:
306  running_apps = []
307  self._app_list.update()
308  rospy.loginfo("done listing apps")
309  return ListAppsResponse(running_apps=running_apps, available_apps=self._app_list.get_app_list())
310 
311  def handle_start_app(self, req):
312  rospy.loginfo("start_app: %s"%(req.name))
313 
314  appname = req.name
315  rospy.loginfo("Loading app: %s"%(appname))
316  try:
317  if self._app_list and self._app_list.get_app(appname):
318  app = self._app_list.get_app(appname)
319  else:
320  app = load_AppDefinition_by_name(appname)
321  except ValueError as e:
322  return StartAppResponse(started=False, message=str(e), error_code=StatusCodes.BAD_REQUEST)
323  except InvalidAppException as e:
324  return StartAppResponse(started=False, message=str(e), error_code=StatusCodes.INTERNAL_ERROR)
325  except NotFoundException as e:
326  return StartAppResponse(started=False, message=str(e), error_code=StatusCodes.NOT_FOUND)
327 
328  # Only support run apps with no plugins to run in parallel
329  # TODO: use multiplexers to enable safe resource sharing
330  rospy.loginfo('Current App: {}'.format(self._current_app))
331  if (self._current_app and
332  (app.launch or app.plugins
333  or not self._current_app_definition.allow_parallel
334  or not app.allow_parallel)):
335  if self._current_app_definition.name == req.name:
336  return StartAppResponse(started=True, message="app [%s] already started"%(req.name), namespace=self._app_interface)
337  elif not self._enable_app_replacement:
338  return StartAppResponse(
339  started=False,
340  message="app [%s] is denied because app [%s] is already running."
341  % (req.name, self._current_app_definition.name),
342  namespace=self._app_interface,
343  error_code=StatusCodes.MULTIAPP_NOT_SUPPORTED)
344  else:
345  self.stop_app(self._current_app_definition.name)
346 
347  try:
348  is_main_app = self._current_app is None
349  has_plugin = not not app.plugins
350 
351  rospy.loginfo('App: {} main: {} plugins: {}'.format(appname, is_main_app, has_plugin))
352  if is_main_app:
353  self._set_current_app(App(name=appname), app)
354 
355  self._status_pub.publish(AppStatus(AppStatus.INFO, 'launching %s'%(app.display_name)))
356 
357  launch_files = []
358  if app.launch:
359  if len(req.args) == 0:
360  launch_files = [app.launch]
361  rospy.loginfo("Launching: {}".format(app.launch))
362  else:
363  app_launch_args = []
364  for arg in req.args:
365  app_launch_args.append("{}:={}".format(arg.key, arg.value))
366  launch_files = [(app.launch, app_launch_args)]
367  rospy.loginfo("Launching: {} {}".format(app.launch, app_launch_args))
368 
369  plugin_launch_files = []
370  if app.plugins:
371  self._current_plugins = []
372  if 'start_plugin_order' in app.plugin_order:
373  plugin_names = [p['name'] for p in app.plugins]
374  plugin_order = app.plugin_order['start_plugin_order']
375  if len(set(plugin_names) - set(plugin_order)) > 0:
376  rospy.logwarn(
377  "Some plugins are defined in plugins but not written in start_plugin_order: {}"
378  .format(set(plugin_names) - set(plugin_order)))
379  app_plugins = []
380  for plugin_name in plugin_order:
381  if plugin_name not in plugin_names:
382  rospy.logerr("app plugin '{}' not found in app file.".format(plugin_name))
383  continue
384  app_plugins.append(
385  app.plugins[plugin_names.index(plugin_name)])
386  else:
387  app_plugins = app.plugins
388  for app_plugin in app_plugins:
389  app_plugin_type = app_plugin['type']
390  try:
391  plugin = next(
392  p for p in self._plugins if p['name'] == app_plugin_type)
393  self._current_plugins.append((app_plugin, plugin))
394  if 'launch' in plugin and plugin['launch']:
395  plugin_launch_file = find_resource(plugin['launch'])
396  launch_args = {}
397  if 'launch_args' in app_plugin:
398  launch_args.update(app_plugin['launch_args'])
399  if 'launch_arg_yaml' in app_plugin:
400  with open(app_plugin['launch_arg_yaml']) as yaml_f:
401  yaml_launch_args = yaml.load(yaml_f)
402  for k, v in yaml_launch_args.items():
403  if k in launch_args:
404  rospy.logwarn("'{}' is set both in launch_args and launch_arg_yaml".format(k))
405  rospy.logwarn("'{}' is overwritten: {} -> {}".format(k, launch_args[k], v))
406  launch_args[k] = v
407  plugin_launch_args = []
408  for k, v in launch_args.items():
409  if isinstance(v, list):
410  v = " ".join(map(str, v))
411  plugin_launch_args.append("{}:={}".format(k, v))
412  rospy.loginfo(
413  "Launching plugin: {} {}".format(
414  plugin_launch_file, plugin_launch_args))
415  plugin_launch_files.append(
416  (plugin_launch_file, plugin_launch_args))
417  except StopIteration:
418  rospy.logerr(
419  'There is no available app_manager plugin: {}'
420  .format(app_plugin_type))
421 
422  #TODO:XXX This is a roslaunch-caller-like abomination. Should leverage a true roslaunch API when it exists.
423  if app.launch:
424  try:
425  self._launch = roslaunch.parent.ROSLaunchParent(
426  rospy.get_param("/run_id"), launch_files,
427  is_core=False, process_listeners=(),
428  sigint_timeout=self._sigint_timeout,
429  sigterm_timeout=self._sigterm_timeout)
430  except TypeError:
431  # ROSLaunchParent() does not have sigint/sigterm_timeout argument
432  # if roslaunch < 1.14.13 or < 1.15.5
433  self._launch = roslaunch.parent.ROSLaunchParent(
434  rospy.get_param("/run_id"), launch_files,
435  is_core=False, process_listeners=())
436  self._launch._load_config()
437  if has_plugin:
438  try:
439  self._plugin_launch = roslaunch.parent.ROSLaunchParent(
440  rospy.get_param("/run_id"), plugin_launch_files,
441  is_core=False, process_listeners=(),
442  sigint_timeout=self._sigint_timeout,
443  sigterm_timeout=self._sigterm_timeout)
444  except TypeError:
445  # ROSLaunchParent() does not have sigint/sigterm_timeout argument
446  # if roslaunch < 1.14.13 or < 1.15.5
447  self._plugin_launch = roslaunch.parent.ROSLaunchParent(
448  rospy.get_param("/run_id"), plugin_launch_files,
449  is_core=False, process_listeners=())
450  self._plugin_launch._load_config()
451 
452  #TODO: convert to method
453  nodes = []
454  if app.launch:
455  nodes.extend(self._launch.config.nodes)
456  if app.run:
457  nodes.append(app.run)
458  if self._enable_topic_remapping:
459  for N in nodes:
460  for t in app.interface.published_topics.keys():
461  N.remap_args.append((t, self._app_interface + '/' + t))
462  for t in app.interface.subscribed_topics.keys():
463  N.remap_args.append((t, self._app_interface + '/' + t))
464 
465  # run plugin modules first
466  if is_main_app:
467  self._current_plugin_processes = []
468  if has_plugin and self._current_plugins:
469  self._plugin_context = {}
470  self._plugin_insts = {}
471  for app_plugin, plugin in self._current_plugins:
472  if 'module' in plugin and plugin['module']:
473  plugin_args = {}
474  start_plugin_args = {}
475  if 'plugin_args' in app_plugin:
476  plugin_args.update(app_plugin['plugin_args'])
477  if 'plugin_arg_yaml' in app_plugin:
478  with open(app_plugin['plugin_arg_yaml']) as yaml_f:
479  yaml_plugin_args = yaml.load(yaml_f)
480  for k, v in yaml_plugin_args.items():
481  if k in plugin_args:
482  rospy.logwarn("'{}' is set both in plugin_args and plugin_arg_yaml".format(k))
483  rospy.logwarn("'{}' is overwritten: {} -> {}".format(k, plugin_args[k], v))
484  plugin_args[k] = v
485  if 'start_plugin_args' in app_plugin:
486  start_plugin_args.update(app_plugin['start_plugin_args'])
487  if 'start_plugin_arg_yaml' in app_plugin:
488  with open(app_plugin['start_plugin_arg_yaml']) as yaml_f:
489  yaml_plugin_args = yaml.load(yaml_f)
490  for k, v in yaml_plugin_args.items():
491  if k in start_plugin_args:
492  rospy.logwarn("'{}' is set both in start_plugin_args and start_plugin_arg_yaml".format(k))
493  rospy.logwarn("'{}' is overwritten: {} -> {}".format(k, start_plugin_args[k], v))
494  start_plugin_args[k] = v
495  plugin_args.update(start_plugin_args)
496  mod = __import__(plugin['module'].split('.')[0])
497  for sub_mod in plugin['module'].split('.')[1:]:
498  mod = getattr(mod, sub_mod)
499  plugin_inst = mod()
500  plugin_inst.app_manager_start_plugin(
501  app, self._plugin_context, plugin_args)
502  self._plugin_insts[plugin['module']] = plugin_inst
503  if 'run' in plugin and plugin['run']:
504  p, a = roslib.names.package_resource_name(plugin['run'])
505  args = plugin.get('run_args', None)
506  node = roslaunch.core.Node(p, a, args=args, output='screen',
507  required=False)
508  proc, success = self._default_launch.runner.launch_node(node)
509  if not success:
510  raise roslaunch.core.RLException(
511  "failed to launch plugin %s/%s"%(node.package, node.type))
512  self._current_plugin_processes.append(proc)
513 
514  # then, start plugin launches
515  if has_plugin:
516  self._plugin_launch.start()
517 
518  # finally launch main launch
519  if app.launch:
520  self._launch.start()
521  if app.run:
522  node = app.run
523  proc, success = self._default_launch.runner.launch_node(node)
524  if not success:
525  raise roslaunch.core.RLException(
526  "failed to launch %s/%s"%(node.package, node.type))
527  if is_main_app:
528  self._current_process = proc
529 
530  if is_main_app and app.timeout is not None:
531  self._start_time = rospy.Time.now()
532 
533  fp = [x for x in app.interface.subscribed_topics.keys()]
534  lp = [x for x in app.interface.published_topics.keys()]
535  if self._enable_topic_remapping:
536  fp = [self._app_interface + '/' + x for x in fp]
537  lp = [self._app_interface + '/' + x for x in lp]
538 
539  self._interface_sync = MasterSync(self._interface_master, foreign_pub_names=fp, local_pub_names=lp)
540  if is_main_app:
541  thread.start_new_thread(self.app_monitor, (app.launch,))
542 
543  return StartAppResponse(started=True, message="app [%s] started"%(appname), namespace=self._app_interface)
544 
545  except Exception as e:
546  rospy.logerr(traceback.format_exc())
547  exc_type, exc_obj, exc_tb = sys.exc_info()
548  fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
549  try:
550  # attempt to kill any launched resources
551  self._stop_current()
552  except:
553  pass
554  finally:
555  self._set_current_app(None, None)
556  self._status_pub.publish(AppStatus(AppStatus.INFO, 'app start failed'))
557  rospy.logerr(
558  "app start failed [{}, line {}: {}]".format(fname, exc_tb.tb_lineno, str(e)))
559  return StartAppResponse(started=False, message="internal error [%s, line %d: %s]"%(fname, exc_tb.tb_lineno, str(e)), error_code=StatusCodes.INTERNAL_ERROR)
560 
561  def _stop_current(self):
562  try:
563  self._stopping = True
564  self.__stop_current()
565  finally:
566  self._launch = None
567  self._plugin_launch = None
568  self._exit_code = None
569  self._stopped = None
570  self._stopping = None
571  self._current_process = None
572  self._timeout = None
573  self._current_plugins = None
574  self._current_plugin_processes = None
575  self._plugin_context = None
576  self._plugin_insts = None
577  self._start_time = None
578  try:
579  self._interface_sync.stop()
580  finally:
581  self._interface_sync = None
582 
583  def __stop_current(self):
584  if self._api_sync:
585  self._api_sync.stop()
586  if self._launch:
587  self._launch.shutdown()
588  if (self._exit_code is None
589  and self._launch.pm
590  and len(self._launch.pm.dead_list) > 0):
591  exit_codes = [p.exit_code for p in self._launch.pm.dead_list]
592  self._exit_code = max(exit_codes)
593  if self._current_process:
594  self._current_process.stop()
595  if (self._exit_code is None
596  and self._default_launch.pm
597  and len(self._default_launch.pm.dead_list) > 0):
598  self._exit_code = self._default_launch.pm.dead_list[0].exit_code
599  if not self._exit_code is None and self._exit_code > 0:
600  rospy.logerr(
601  "App stopped with exit code: {}".format(self._exit_code))
602  if self._plugin_launch:
603  self._plugin_launch.shutdown()
605  for p in self._current_plugin_processes:
606  p.stop()
607  if self._current_plugins:
608  self._plugin_context['exit_code'] = self._exit_code
609  self._plugin_context['stopped'] = self._stopped
610  self._plugin_context['timeout'] = self._timeout
611  if 'stop_plugin_order' in self._current_app_definition.plugin_order:
612  plugin_names = [p['name'] for p in self._current_app_definition.plugins]
613  plugin_order = self._current_app_definition.plugin_order['stop_plugin_order']
614  if len(set(plugin_names) - set(plugin_order)) > 0:
615  rospy.logwarn(
616  "Some plugins are defined in plugins but not written in stop_plugin_order: {}"
617  .format(set(plugin_names) - set(plugin_order)))
618  current_plugins = []
619  for plugin_name in plugin_order:
620  if plugin_name not in plugin_names:
621  rospy.logerr("app plugin '{}' not found in app file.".format(plugin_name))
622  continue
623  current_plugin_names = [p['name'] for p, _ in self._current_plugins]
624  if plugin_name not in current_plugin_names:
625  rospy.logwarn("app plugin '{}' is not running, so skip stopping".format(plugin_name))
626  continue
627  current_plugins.append(
628  self._current_plugins[current_plugin_names.index(plugin_name)])
629  else:
630  current_plugins = self._current_plugins
631  for app_plugin, plugin in current_plugins:
632  if 'module' in plugin and plugin['module']:
633  plugin_args = {}
634  stop_plugin_args = {}
635  if 'plugin_args' in app_plugin:
636  plugin_args.update(app_plugin['plugin_args'])
637  if 'plugin_arg_yaml' in app_plugin:
638  with open(app_plugin['plugin_arg_yaml']) as yaml_f:
639  yaml_plugin_args = yaml.load(yaml_f)
640  for k, v in yaml_plugin_args.items():
641  if k in plugin_args:
642  rospy.logwarn("'{}' is set both in plugin_args and plugin_arg_yaml".format(k))
643  rospy.logwarn("'{}' is overwritten: {} -> {}".format(k, plugin_args[k], v))
644  plugin_args[k] = v
645  if 'stop_plugin_args' in app_plugin:
646  stop_plugin_args.update(app_plugin['stop_plugin_args'])
647  if 'stop_plugin_arg_yaml' in app_plugin:
648  with open(app_plugin['stop_plugin_arg_yaml']) as yaml_f:
649  yaml_plugin_args = yaml.load(yaml_f)
650  for k, v in yaml_plugin_args.items():
651  if k in stop_plugin_args:
652  rospy.logwarn("'{}' is set both in stop_plugin_args and stop_plugin_arg_yaml".format(k))
653  rospy.logwarn("'{}' is overwritten: {} -> {}".format(k, stop_plugin_args[k], v))
654  stop_plugin_args[k] = v
655  plugin_args.update(stop_plugin_args)
656  if plugin['module'] in self._plugin_insts:
657  plugin_inst = self._plugin_insts[plugin['module']]
658  else:
659  mod = __import__(plugin['module'].split('.')[0])
660  for sub_mod in plugin['module'].split('.')[1:]:
661  mod = getattr(mod, sub_mod)
662  plugin_inst = mod()
663  plugin_inst.app_manager_stop_plugin(
665  self._plugin_context, plugin_args)
666 
667  def handle_stop_app(self, req):
668  rospy.loginfo("handle stop app: %s"%(req.name))
669  self._stopped = True
670  return self.stop_app(req.name)
671 
672  def handle_reload_app_list(self, req=None):
673  try:
674  self._app_list.update()
675  self.publish_list_apps()
677  rospy.loginfo("app list is reloaded")
678  except Exception as e:
679  rospy.logerr("Failed to reload app list: %s" % e)
680  return EmptyResponse()
681 
682  def app_monitor(self, is_launch):
683  def get_target():
684  if is_launch:
685  return self._launch
686  return self._current_process
687  def is_done(target):
688  if is_launch:
689  return (not target.pm or target.pm.done)
690  return target.stopped
691  def check_required(target):
692  # required nodes are not registered to the dead_list when finished
693  # so we need to constantly check its return value
694  if is_launch and target.pm:
695  # run nodes are never registered as required
696  procs = target.pm.procs[:]
697  exit_codes = [p.exit_code for p in procs if p.required]
698  if exit_codes:
699  self._exit_code = max(exit_codes)
700 
701  while get_target():
702  time.sleep(0.1)
703  target = get_target()
704  timeout = self._current_app_definition.timeout
705  appname = self._current_app_definition.name
706  now = rospy.Time.now()
707  if target:
708  check_required(target)
709  if is_done(target):
710  time.sleep(1.0)
711  if not self._stopping:
712  self.stop_app(appname)
713  break
714  if (timeout is not None and
715  self._start_time is not None and
716  (now - self._start_time).to_sec() > timeout):
717  self._stopped = True
718  self._timeout = True
719  self.stop_app(appname)
720  rospy.logerr(
721  'app {} is stopped because of timeout: {}s'.format(
722  appname, timeout))
723  break
724 
725 
726 
727  def stop_app(self, appname):
728  resp = StopAppResponse(stopped=False)
729  try:
730  app = self._current_app_definition
731 
732  # request to stop all apps.
733  if app is not None and appname == '*':
734  appname = app.name
735 
736  if app is None or app.name != appname:
737  rospy.loginfo("handle stop app: app [%s] is not running [x]"%(appname))
738  resp.error_code = StatusCodes.NOT_RUNNING
739  resp.message = "app %s is not running"%(appname)
740  else:
741  try:
742  app_status_message = None
743  if self._launch or self._current_process:
744  rosinfo_message = "handle stop app: stopping app [%s]"%(appname)
745  app_status_message = 'stopping %s'%(app.display_name)
746  self._stop_current()
747  resp.stopped = True
748  resp.message = "%s stopped"%(appname)
749  if self._timeout:
750  resp.timeout = self._timeout
751  rosinfo_message += "by timeout"
752  app_status_message += "by timeout"
753  resp.message += " by timeout"
754  rospy.loginfo(rosinfo_message)
755  else:
756  rospy.loginfo("handle stop app: app [%s] is not running"%(appname))
757  resp.message = "app [%s] is not running"%(appname)
758  resp.error_code = StatusCodes.NOT_RUNNING
759  finally:
760  if app_status_message is not None:
761  self._status_pub.publish(
762  AppStatus(AppStatus.INFO, app_status_message))
763  self._launch = None
764  self._current_process = None
765  self._set_current_app(None, None)
766 
767  except Exception as e:
768  rospy.logerr(traceback.format_exc())
769  exc_type, exc_obj, exc_tb = sys.exc_info()
770  fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
771  rospy.logerr("handle stop app: internal error [%s, line %d: %s]"%(fname, exc_tb.tb_lineno, str(e)))
772  resp.error_code = StatusCodes.INTERNAL_ERROR
773  resp.message = "internal error: %s"%(str(e))
774 
775  return resp
def find_resource(resource, rospack=None)
Definition: app.py:122
def app_monitor(self, is_launch)
Definition: app_manager.py:682
def __init__(self, robot_name, interface_master, app_list, exchange, plugins=None, enable_app_replacement=True, enable_topic_remapping=True, sigint_timeout=15.0, sigterm_timeout=2.0)
Definition: app_manager.py:132
def handle_list_exchange_apps(self, req)
Definition: app_manager.py:256
def load_AppDefinition_by_name(appname, rospack=None)
Definition: app.py:392
def _set_current_app(self, app, app_definition)
Definition: app_manager.py:240
def _load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=True, ignore_unset_args=False)
Definition: app_manager.py:75
def handle_reload_app_list(self, req=None)
Definition: app_manager.py:672


app_manager
Author(s): Jeremy Leibs, Ken Conley, Yuki Furuta
autogenerated on Thu Oct 13 2022 02:59:17