1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 """
36 Local process implementation for running and monitoring nodes.
37 """
38
39 import os
40 import sys
41 import signal
42 import socket
43 import subprocess
44 import time
45 import traceback
46
47 import roslib.rosenv
48 import roslib.network
49
50 from roslaunch.core import *
51 from roslaunch.node_args import create_local_process_env, create_local_process_args
52 from roslaunch.pmon import Process, FatalProcessLaunch
53
54 import logging
55 _logger = logging.getLogger("roslaunch")
56
57 _TIMEOUT_SIGINT = 15.0
58 _TIMEOUT_SIGTERM = 2.0
59
60 _counter = 0
65
67 """
68 Launch a master
69 @param type_: name of master executable
70 @type type_: str
71 @param ros_root: ROS_ROOT environment setting
72 @type ros_root: str
73 @param port: port to launch master on
74 @type port: int
75 @raise RLException: if type_ or port is invalid
76 """
77 if port < 1 or port > 65535:
78 raise RLException("invalid port assignment: %s"%port)
79
80 _logger.info("create_master_process: %s, %s, %s", type_, ros_root, port)
81 master = os.path.join(ros_root, 'bin', type_)
82
83 if type_ == Master.ROSMASTER:
84 package = 'rosmaster'
85 args = [master, '--core', '-p', str(port)]
86 else:
87 raise RLException("unknown master typ_: %s"%type_)
88
89 _logger.info("process[master]: launching with args [%s]"%args)
90 log_output = False
91 return LocalProcess(run_id, package, 'master', args, os.environ, log_output, None)
92
94 """
95 Factory for generating processes for launching local ROS
96 nodes. Also registers the process with the L{ProcessMonitor} so that
97 events can be generated when the process dies.
98
99 @param run_id: run_id of launch
100 @type run_id: str
101 @param node: node to launch. Node name must be assigned.
102 @type node: L{Node}
103 @param master_uri: API URI for master node
104 @type master_uri: str
105 @return: local process instance
106 @rtype: L{LocalProcess}
107 @raise NodeParamsException: If the node's parameters are improperly specific
108 """
109 _logger.info("create_node_process: package[%s] type[%s] machine[%s] master_uri[%s]", node.package, node.type, node.machine, master_uri)
110
111 machine = node.machine
112 if machine is None:
113 raise RLException("Internal error: no machine selected for node of type [%s/%s]"%(node.package, node.type))
114 if not node.name:
115 raise ValueError("node name must be assigned")
116
117
118 env = create_local_process_env(node, machine, master_uri)
119
120 if not node.name:
121 raise ValueError("node name must be assigned")
122
123
124
125 name = "%s-%s"%(node.name, _next_counter())
126
127 _logger.info('process[%s]: env[%s]', name, env)
128
129 args = create_local_process_args(node, machine)
130 _logger.info('process[%s]: args[%s]', name, args)
131
132
133 log_output = node.output != 'screen'
134 _logger.debug('process[%s]: returning LocalProcess wrapper')
135 return LocalProcess(run_id, node.package, name, args, env, log_output, respawn=node.respawn, required=node.required, cwd=node.cwd)
136
137
138
140 """
141 Process launched on local machine
142 """
143
144 - def __init__(self, run_id, package, name, args, env, log_output, respawn=False, required=False, cwd=None, is_node=True):
145 """
146 @param run_id: unique run ID for this roslaunch. Used to
147 generate log directory location. run_id may be None if this
148 feature is not being used.
149 @type run_id: str
150 @param package: name of package process is part of
151 @type package: str
152 @param name: name of process
153 @type name: str
154 @param args: list of arguments to process
155 @type args: [str]
156 @param env: environment dictionary for process
157 @type env: {str : str}
158 @param log_output: if True, log output streams of process
159 @type log_output: bool
160 @param respawn: respawn process if it dies (default is False)
161 @type respawn: bool
162 @param cwd: working directory of process, or None
163 @type cwd: str
164 @param is_node: (optional) if True, process is ROS node and accepts ROS node command-line arguments. Default: True
165 @type is_node: False
166 """
167 super(LocalProcess, self).__init__(package, name, args, env, respawn, required)
168 self.run_id = run_id
169 self.popen = None
170 self.log_output = log_output
171 self.started = False
172 self.stopped = False
173 self.cwd = cwd
174 self.log_dir = None
175 self.pid = -1
176 self.is_node = is_node
177
178
180 """
181 Get all data about this process in dictionary form
182 """
183 info = super(LocalProcess, self).get_info()
184 info['pid'] = self.pid
185 if self.run_id:
186 info['run_id'] = self.run_id
187 info['log_output'] = self.log_output
188 if self.cwd is not None:
189 info['cwd'] = self.cwd
190 return info
191
238
240 """
241 Start the process.
242
243 @raise FatalProcessLaunch: if process cannot be started and it
244 is not likely to ever succeed
245 """
246 super(LocalProcess, self).start()
247 try:
248 self.lock.acquire()
249 if self.started:
250 _logger.info("process[%s]: restarting os process", self.name)
251 else:
252 _logger.info("process[%s]: starting os process", self.name)
253 self.started = self.stopped = False
254
255 full_env = self.env
256
257
258 try:
259 logfileout, logfileerr = self._configure_logging()
260 except Exception, e:
261 _logger.error(traceback.format_exc())
262 printerrlog("[%s] ERROR: unable to configure logging [%s]"%(self.name, str(e)))
263
264
265
266 logfileout, logfileerr = subprocess.PIPE, subprocess.PIPE
267
268 if self.cwd == 'node':
269 cwd = os.path.dirname(self.args[0])
270 elif self.cwd == 'cwd':
271 cwd = os.getcwd()
272 elif self.cwd == 'ros-root':
273 cwd = get_ros_root()
274 else:
275 cwd = roslib.rosenv.get_ros_home()
276
277 _logger.info("process[%s]: start w/ args [%s]", self.name, self.args)
278 _logger.info("process[%s]: cwd will be [%s]", self.name, cwd)
279
280 try:
281 self.popen = subprocess.Popen(self.args, cwd=cwd, stdout=logfileout, stderr=logfileerr, env=full_env, close_fds=True, preexec_fn=os.setsid)
282 except OSError, (errno, msg):
283 self.started = True
284 _logger.error("OSError(%d, %s)", errno, msg)
285 if errno == 8:
286 raise FatalProcessLaunch("Unable to launch [%s]. \nIf it is a script, you may be missing a '#!' declaration at the top."%self.name)
287 elif errno == 2:
288 raise FatalProcessLaunch("""Roslaunch got a '%s' error while attempting to run:
289
290 %s
291
292 Please make sure that all the executables in this command exist and have
293 executable permission. This is often caused by a bad launch-prefix."""%(msg, ' '.join(self.args)))
294 else:
295 raise FatalProcessLaunch("unable to launch [%s]: %s"%(' '.join(self.args), msg))
296
297 self.started = True
298
299
300
301 poll_result = self.popen.poll()
302 if poll_result is None or poll_result == 0:
303 self.pid = self.popen.pid
304 printlog_bold("process[%s]: started with pid [%s]"%(self.name, self.pid))
305 return True
306 else:
307 printerrlog("failed to start local process: %s"%(' '.join(self.args)))
308 return False
309 finally:
310 self.lock.release()
311
313 """
314 @return: True if process is still running
315 @rtype: bool
316 """
317 if not self.started:
318 return True
319 if self.stopped or self.popen is None:
320 return False
321 self.exit_code = self.popen.poll()
322 if self.exit_code is not None:
323 return False
324 return True
325
327 """
328 @return: human-readable description of exit state
329 @rtype: str
330 """
331
332 if self.exit_code is not None:
333 if self.exit_code:
334 if self.log_dir:
335 return 'process has died [pid %s, exit code %s].\nlog files: %s*.log'%(self.pid, self.exit_code, os.path.join(self.log_dir, self.name))
336 else:
337 return 'process has died [pid %s, exit code %s]'%(self.pid, self.exit_code)
338 else:
339 if self.log_dir:
340 return 'process has finished cleanly.\nlog file: %s*.log'%(os.path.join(self.log_dir, self.name))
341 else:
342 return 'process has finished cleanly'
343 else:
344 return 'process has died'
345
347 """
348 UNIX implementation of process killing
349
350 @param errors: error messages. stop() will record messages into this list.
351 @type errors: [str]
352 """
353 self.exit_code = self.popen.poll()
354 if self.exit_code is not None:
355 _logger.debug("process[%s].stop(): process has already returned %s", self.name, self.exit_code)
356
357 self.popen = None
358 self.stopped = True
359 return
360
361 pid = self.popen.pid
362 pgid = os.getpgid(pid)
363 _logger.info("process[%s]: killing os process with pid[%s] pgid[%s]", self.name, pid, pgid)
364
365 try:
366
367 _logger.info("[%s] sending SIGINT to pgid [%s]", self.name, pgid)
368 os.killpg(pgid, signal.SIGINT)
369 _logger.info("[%s] sent SIGINT to pgid [%s]", self.name, pgid)
370 timeout_t = time.time() + _TIMEOUT_SIGINT
371 retcode = self.popen.poll()
372 while time.time() < timeout_t and retcode is None:
373 time.sleep(0.1)
374 retcode = self.popen.poll()
375
376 if retcode is None:
377 printerrlog("[%s] escalating to SIGTERM"%self.name)
378 timeout_t = time.time() + _TIMEOUT_SIGTERM
379 os.killpg(pgid, signal.SIGTERM)
380 _logger.info("[%s] sent SIGTERM to pgid [%s]"%(self.name, pgid))
381 retcode = self.popen.poll()
382 while time.time() < timeout_t and retcode is None:
383 time.sleep(0.2)
384 _logger.debug('poll for retcode')
385 retcode = self.popen.poll()
386 if retcode is None:
387 printerrlog("[%s] escalating to SIGKILL"%self.name)
388 errors.append("process[%s, pid %s]: required SIGKILL. May still be running."%(self.name, pid))
389 try:
390 os.killpg(pgid, signal.SIGKILL)
391 _logger.info("[%s] sent SIGKILL to pgid [%s]"%(self.name, pgid))
392
393
394
395 _logger.info("process[%s]: sent SIGKILL", self.name)
396 except OSError, e:
397 if e.args[0] == 3:
398 printerrlog("no [%s] process with pid [%s]"%(self.name, pid))
399 else:
400 printerrlog("errors shutting down [%s], see log for details"%self.name)
401 _logger.error(traceback.format_exc())
402 else:
403 _logger.info("process[%s]: SIGTERM killed with return value %s", self.name, retcode)
404 else:
405 _logger.info("process[%s]: SIGINT killed with return value %s", self.name, retcode)
406
407 finally:
408 self.popen = None
409
410 - def stop(self, errors=[]):
411 """
412 Stop the process. Record any significant error messages in the errors parameter
413
414 @param errors: error messages. stop() will record messages into this list.
415 @type errors: [str]
416 """
417 super(LocalProcess, self).stop(errors)
418 self.lock.acquire()
419 try:
420 try:
421 _logger.debug("process[%s].stop() starting", self.name)
422 if self.popen is None:
423 _logger.debug("process[%s].stop(): popen is None, nothing to kill")
424 return
425
426
427 self._stop_unix(errors)
428 except:
429
430 _logger.error("[%s] EXCEPTION %s", self.name, traceback.format_exc())
431 finally:
432 self.stopped = True
433 self.lock.release()
434
435
436
438 """
439 Remove all instances of args that start with prefix. This is used
440 to remove args that were previously added (and are now being
441 regenerated due to respawning)
442 """
443 existing_args = [a for a in args if a.startswith(prefix)]
444 for a in existing_args:
445 args.remove(a)
446 return args
447